├── .gitignore ├── ESP8266WebServer.h ├── Parsing.cpp ├── README.md ├── SDL_ESP32_BC24_GETIP.h ├── SDL_ESP32_BC24_GETIP.ino ├── WebServer.cpp ├── WebServer.h ├── WiFiManager.cpp ├── WiFiManager.h └── detail ├── RequestHandler.h └── RequestHandlersImpl.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | tx* 3 | .*.swp 4 | *.xml 5 | *.temp 6 | *.test 7 | nohup.out 8 | .*DS_Store 9 | -------------------------------------------------------------------------------- /ESP8266WebServer.h: -------------------------------------------------------------------------------- 1 | /* 2 | ESP8266WebServer.h - Dead simple web-server. 3 | Supports only one simultaneous client, knows how to handle GET and POST. 4 | 5 | Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) 21 | */ 22 | 23 | 24 | #ifndef ESP8266WEBSERVER_H 25 | #define ESP8266WEBSERVER_H 26 | 27 | #include 28 | 29 | #endif //ESP8266WEBSERVER_H 30 | -------------------------------------------------------------------------------- /Parsing.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Parsing.cpp - HTTP request parsing. 3 | 4 | Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) 20 | */ 21 | 22 | #include 23 | #include "WiFiServer.h" 24 | #include "WiFiClient.h" 25 | #include "WebServer.h" 26 | 27 | //#define DEBUG_ESP_HTTP_SERVER 28 | #ifdef DEBUG_ESP_PORT 29 | #define DEBUG_OUTPUT DEBUG_ESP_PORT 30 | #else 31 | #define DEBUG_OUTPUT Serial 32 | #endif 33 | 34 | static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms) 35 | { 36 | char *buf = nullptr; 37 | dataLength = 0; 38 | while (dataLength < maxLength) { 39 | int tries = timeout_ms; 40 | size_t newLength; 41 | while (!(newLength = client.available()) && tries--) delay(1); 42 | if (!newLength) { 43 | break; 44 | } 45 | if (!buf) { 46 | buf = (char *) malloc(newLength + 1); 47 | if (!buf) { 48 | return nullptr; 49 | } 50 | } 51 | else { 52 | char* newBuf = (char *) realloc(buf, dataLength + newLength + 1); 53 | if (!newBuf) { 54 | free(buf); 55 | return nullptr; 56 | } 57 | buf = newBuf; 58 | } 59 | client.readBytes(buf + dataLength, newLength); 60 | dataLength += newLength; 61 | buf[dataLength] = '\0'; 62 | } 63 | return buf; 64 | } 65 | 66 | bool WebServer::_parseRequest(WiFiClient& client) { 67 | // Read the first line of HTTP request 68 | String req = client.readStringUntil('\r'); 69 | client.readStringUntil('\n'); 70 | //reset header value 71 | for (int i = 0; i < _headerKeysCount; ++i) { 72 | _currentHeaders[i].value =String(); 73 | } 74 | 75 | // First line of HTTP request looks like "GET /path HTTP/1.1" 76 | // Retrieve the "/path" part by finding the spaces 77 | int addr_start = req.indexOf(' '); 78 | int addr_end = req.indexOf(' ', addr_start + 1); 79 | if (addr_start == -1 || addr_end == -1) { 80 | #ifdef DEBUG_ESP_HTTP_SERVER 81 | DEBUG_OUTPUT.print("Invalid request: "); 82 | DEBUG_OUTPUT.println(req); 83 | #endif 84 | return false; 85 | } 86 | 87 | String methodStr = req.substring(0, addr_start); 88 | String url = req.substring(addr_start + 1, addr_end); 89 | String versionEnd = req.substring(addr_end + 8); 90 | _currentVersion = atoi(versionEnd.c_str()); 91 | String searchStr = ""; 92 | int hasSearch = url.indexOf('?'); 93 | if (hasSearch != -1){ 94 | searchStr = urlDecode(url.substring(hasSearch + 1)); 95 | url = url.substring(0, hasSearch); 96 | } 97 | _currentUri = url; 98 | _chunked = false; 99 | 100 | HTTPMethod method = HTTP_GET; 101 | if (methodStr == "POST") { 102 | method = HTTP_POST; 103 | } else if (methodStr == "DELETE") { 104 | method = HTTP_DELETE; 105 | } else if (methodStr == "OPTIONS") { 106 | method = HTTP_OPTIONS; 107 | } else if (methodStr == "PUT") { 108 | method = HTTP_PUT; 109 | } else if (methodStr == "PATCH") { 110 | method = HTTP_PATCH; 111 | } 112 | _currentMethod = method; 113 | 114 | #ifdef DEBUG_ESP_HTTP_SERVER 115 | DEBUG_OUTPUT.print("method: "); 116 | DEBUG_OUTPUT.print(methodStr); 117 | DEBUG_OUTPUT.print(" url: "); 118 | DEBUG_OUTPUT.print(url); 119 | DEBUG_OUTPUT.print(" search: "); 120 | DEBUG_OUTPUT.println(searchStr); 121 | #endif 122 | 123 | //attach handler 124 | RequestHandler* handler; 125 | for (handler = _firstHandler; handler; handler = handler->next()) { 126 | if (handler->canHandle(_currentMethod, _currentUri)) 127 | break; 128 | } 129 | _currentHandler = handler; 130 | 131 | String formData; 132 | // below is needed only when POST type request 133 | if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){ 134 | String boundaryStr; 135 | String headerName; 136 | String headerValue; 137 | bool isForm = false; 138 | bool isEncoded = false; 139 | uint32_t contentLength = 0; 140 | //parse headers 141 | while(1){ 142 | req = client.readStringUntil('\r'); 143 | client.readStringUntil('\n'); 144 | if (req == "") break;//no moar headers 145 | int headerDiv = req.indexOf(':'); 146 | if (headerDiv == -1){ 147 | break; 148 | } 149 | headerName = req.substring(0, headerDiv); 150 | headerValue = req.substring(headerDiv + 1); 151 | headerValue.trim(); 152 | _collectHeader(headerName.c_str(),headerValue.c_str()); 153 | 154 | #ifdef DEBUG_ESP_HTTP_SERVER 155 | DEBUG_OUTPUT.print("headerName: "); 156 | DEBUG_OUTPUT.println(headerName); 157 | DEBUG_OUTPUT.print("headerValue: "); 158 | DEBUG_OUTPUT.println(headerValue); 159 | #endif 160 | 161 | if (headerName.equalsIgnoreCase("Content-Type")){ 162 | if (headerValue.startsWith("text/plain")){ 163 | isForm = false; 164 | } else if (headerValue.startsWith("application/x-www-form-urlencoded")){ 165 | isForm = false; 166 | isEncoded = true; 167 | } else if (headerValue.startsWith("multipart/")){ 168 | boundaryStr = headerValue.substring(headerValue.indexOf('=')+1); 169 | isForm = true; 170 | } 171 | } else if (headerName.equalsIgnoreCase("Content-Length")){ 172 | contentLength = headerValue.toInt(); 173 | } else if (headerName.equalsIgnoreCase("Host")){ 174 | _hostHeader = headerValue; 175 | } 176 | } 177 | 178 | if (!isForm){ 179 | size_t plainLength; 180 | char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT); 181 | if (plainLength < contentLength) { 182 | free(plainBuf); 183 | return false; 184 | } 185 | if (contentLength > 0) { 186 | if (searchStr != "") searchStr += '&'; 187 | if(isEncoded){ 188 | //url encoded form 189 | String decoded = urlDecode(plainBuf); 190 | size_t decodedLen = decoded.length(); 191 | memcpy(plainBuf, decoded.c_str(), decodedLen); 192 | plainBuf[decodedLen] = 0; 193 | searchStr += plainBuf; 194 | } 195 | _parseArguments(searchStr); 196 | if(!isEncoded){ 197 | //plain post json or other data 198 | RequestArgument& arg = _currentArgs[_currentArgCount++]; 199 | arg.key = "plain"; 200 | arg.value = String(plainBuf); 201 | } 202 | 203 | #ifdef DEBUG_ESP_HTTP_SERVER 204 | DEBUG_OUTPUT.print("Plain: "); 205 | DEBUG_OUTPUT.println(plainBuf); 206 | #endif 207 | free(plainBuf); 208 | } 209 | } 210 | 211 | if (isForm){ 212 | _parseArguments(searchStr); 213 | if (!_parseForm(client, boundaryStr, contentLength)) { 214 | return false; 215 | } 216 | } 217 | } else { 218 | String headerName; 219 | String headerValue; 220 | //parse headers 221 | while(1){ 222 | req = client.readStringUntil('\r'); 223 | client.readStringUntil('\n'); 224 | if (req == "") break;//no moar headers 225 | int headerDiv = req.indexOf(':'); 226 | if (headerDiv == -1){ 227 | break; 228 | } 229 | headerName = req.substring(0, headerDiv); 230 | headerValue = req.substring(headerDiv + 2); 231 | _collectHeader(headerName.c_str(),headerValue.c_str()); 232 | 233 | #ifdef DEBUG_ESP_HTTP_SERVER 234 | DEBUG_OUTPUT.print("headerName: "); 235 | DEBUG_OUTPUT.println(headerName); 236 | DEBUG_OUTPUT.print("headerValue: "); 237 | DEBUG_OUTPUT.println(headerValue); 238 | #endif 239 | 240 | if (headerName.equalsIgnoreCase("Host")){ 241 | _hostHeader = headerValue; 242 | } 243 | } 244 | _parseArguments(searchStr); 245 | } 246 | client.flush(); 247 | 248 | #ifdef DEBUG_ESP_HTTP_SERVER 249 | DEBUG_OUTPUT.print("Request: "); 250 | DEBUG_OUTPUT.println(url); 251 | DEBUG_OUTPUT.print(" Arguments: "); 252 | DEBUG_OUTPUT.println(searchStr); 253 | #endif 254 | 255 | return true; 256 | } 257 | 258 | bool WebServer::_collectHeader(const char* headerName, const char* headerValue) { 259 | for (int i = 0; i < _headerKeysCount; i++) { 260 | if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) { 261 | _currentHeaders[i].value=headerValue; 262 | return true; 263 | } 264 | } 265 | return false; 266 | } 267 | 268 | void WebServer::_parseArguments(String data) { 269 | #ifdef DEBUG_ESP_HTTP_SERVER 270 | DEBUG_OUTPUT.print("args: "); 271 | DEBUG_OUTPUT.println(data); 272 | #endif 273 | if (_currentArgs) 274 | delete[] _currentArgs; 275 | _currentArgs = 0; 276 | if (data.length() == 0) { 277 | _currentArgCount = 0; 278 | _currentArgs = new RequestArgument[1]; 279 | return; 280 | } 281 | _currentArgCount = 1; 282 | 283 | for (int i = 0; i < (int)data.length(); ) { 284 | i = data.indexOf('&', i); 285 | if (i == -1) 286 | break; 287 | ++i; 288 | ++_currentArgCount; 289 | } 290 | #ifdef DEBUG_ESP_HTTP_SERVER 291 | DEBUG_OUTPUT.print("args count: "); 292 | DEBUG_OUTPUT.println(_currentArgCount); 293 | #endif 294 | 295 | _currentArgs = new RequestArgument[_currentArgCount+1]; 296 | int pos = 0; 297 | int iarg; 298 | for (iarg = 0; iarg < _currentArgCount;) { 299 | int equal_sign_index = data.indexOf('=', pos); 300 | int next_arg_index = data.indexOf('&', pos); 301 | #ifdef DEBUG_ESP_HTTP_SERVER 302 | DEBUG_OUTPUT.print("pos "); 303 | DEBUG_OUTPUT.print(pos); 304 | DEBUG_OUTPUT.print("=@ "); 305 | DEBUG_OUTPUT.print(equal_sign_index); 306 | DEBUG_OUTPUT.print(" &@ "); 307 | DEBUG_OUTPUT.println(next_arg_index); 308 | #endif 309 | if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) { 310 | #ifdef DEBUG_ESP_HTTP_SERVER 311 | DEBUG_OUTPUT.print("arg missing value: "); 312 | DEBUG_OUTPUT.println(iarg); 313 | #endif 314 | if (next_arg_index == -1) 315 | break; 316 | pos = next_arg_index + 1; 317 | continue; 318 | } 319 | RequestArgument& arg = _currentArgs[iarg]; 320 | arg.key = data.substring(pos, equal_sign_index); 321 | arg.value = data.substring(equal_sign_index + 1, next_arg_index); 322 | #ifdef DEBUG_ESP_HTTP_SERVER 323 | DEBUG_OUTPUT.print("arg "); 324 | DEBUG_OUTPUT.print(iarg); 325 | DEBUG_OUTPUT.print(" key: "); 326 | DEBUG_OUTPUT.print(arg.key); 327 | DEBUG_OUTPUT.print(" value: "); 328 | DEBUG_OUTPUT.println(arg.value); 329 | #endif 330 | ++iarg; 331 | if (next_arg_index == -1) 332 | break; 333 | pos = next_arg_index + 1; 334 | } 335 | _currentArgCount = iarg; 336 | #ifdef DEBUG_ESP_HTTP_SERVER 337 | DEBUG_OUTPUT.print("args count: "); 338 | DEBUG_OUTPUT.println(_currentArgCount); 339 | #endif 340 | 341 | } 342 | 343 | void WebServer::_uploadWriteByte(uint8_t b){ 344 | if (_currentUpload.currentSize == HTTP_UPLOAD_BUFLEN){ 345 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 346 | _currentHandler->upload(*this, _currentUri, _currentUpload); 347 | _currentUpload.totalSize += _currentUpload.currentSize; 348 | _currentUpload.currentSize = 0; 349 | } 350 | _currentUpload.buf[_currentUpload.currentSize++] = b; 351 | } 352 | 353 | uint8_t WebServer::_uploadReadByte(WiFiClient& client){ 354 | int res = client.read(); 355 | if(res == -1){ 356 | while(!client.available() && client.connected()) 357 | yield(); 358 | res = client.read(); 359 | } 360 | return (uint8_t)res; 361 | } 362 | 363 | bool WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){ 364 | (void) len; 365 | #ifdef DEBUG_ESP_HTTP_SERVER 366 | DEBUG_OUTPUT.print("Parse Form: Boundary: "); 367 | DEBUG_OUTPUT.print(boundary); 368 | DEBUG_OUTPUT.print(" Length: "); 369 | DEBUG_OUTPUT.println(len); 370 | #endif 371 | String line; 372 | int retry = 0; 373 | do { 374 | line = client.readStringUntil('\r'); 375 | ++retry; 376 | } while (line.length() == 0 && retry < 3); 377 | 378 | client.readStringUntil('\n'); 379 | //start reading the form 380 | if (line == ("--"+boundary)){ 381 | RequestArgument* postArgs = new RequestArgument[32]; 382 | int postArgsLen = 0; 383 | while(1){ 384 | String argName; 385 | String argValue; 386 | String argType; 387 | String argFilename; 388 | bool argIsFile = false; 389 | 390 | line = client.readStringUntil('\r'); 391 | client.readStringUntil('\n'); 392 | if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase("Content-Disposition")){ 393 | int nameStart = line.indexOf('='); 394 | if (nameStart != -1){ 395 | argName = line.substring(nameStart+2); 396 | nameStart = argName.indexOf('='); 397 | if (nameStart == -1){ 398 | argName = argName.substring(0, argName.length() - 1); 399 | } else { 400 | argFilename = argName.substring(nameStart+2, argName.length() - 1); 401 | argName = argName.substring(0, argName.indexOf('"')); 402 | argIsFile = true; 403 | #ifdef DEBUG_ESP_HTTP_SERVER 404 | DEBUG_OUTPUT.print("PostArg FileName: "); 405 | DEBUG_OUTPUT.println(argFilename); 406 | #endif 407 | //use GET to set the filename if uploading using blob 408 | if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename"); 409 | } 410 | #ifdef DEBUG_ESP_HTTP_SERVER 411 | DEBUG_OUTPUT.print("PostArg Name: "); 412 | DEBUG_OUTPUT.println(argName); 413 | #endif 414 | argType = "text/plain"; 415 | line = client.readStringUntil('\r'); 416 | client.readStringUntil('\n'); 417 | if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase("Content-Type")){ 418 | argType = line.substring(line.indexOf(':')+2); 419 | //skip next line 420 | client.readStringUntil('\r'); 421 | client.readStringUntil('\n'); 422 | } 423 | #ifdef DEBUG_ESP_HTTP_SERVER 424 | DEBUG_OUTPUT.print("PostArg Type: "); 425 | DEBUG_OUTPUT.println(argType); 426 | #endif 427 | if (!argIsFile){ 428 | while(1){ 429 | line = client.readStringUntil('\r'); 430 | client.readStringUntil('\n'); 431 | if (line.startsWith("--"+boundary)) break; 432 | if (argValue.length() > 0) argValue += "\n"; 433 | argValue += line; 434 | } 435 | #ifdef DEBUG_ESP_HTTP_SERVER 436 | DEBUG_OUTPUT.print("PostArg Value: "); 437 | DEBUG_OUTPUT.println(argValue); 438 | DEBUG_OUTPUT.println(); 439 | #endif 440 | 441 | RequestArgument& arg = postArgs[postArgsLen++]; 442 | arg.key = argName; 443 | arg.value = argValue; 444 | 445 | if (line == ("--"+boundary+"--")){ 446 | #ifdef DEBUG_ESP_HTTP_SERVER 447 | DEBUG_OUTPUT.println("Done Parsing POST"); 448 | #endif 449 | break; 450 | } 451 | } else { 452 | _currentUpload.status = UPLOAD_FILE_START; 453 | _currentUpload.name = argName; 454 | _currentUpload.filename = argFilename; 455 | _currentUpload.type = argType; 456 | _currentUpload.totalSize = 0; 457 | _currentUpload.currentSize = 0; 458 | #ifdef DEBUG_ESP_HTTP_SERVER 459 | DEBUG_OUTPUT.print("Start File: "); 460 | DEBUG_OUTPUT.print(_currentUpload.filename); 461 | DEBUG_OUTPUT.print(" Type: "); 462 | DEBUG_OUTPUT.println(_currentUpload.type); 463 | #endif 464 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 465 | _currentHandler->upload(*this, _currentUri, _currentUpload); 466 | _currentUpload.status = UPLOAD_FILE_WRITE; 467 | uint8_t argByte = _uploadReadByte(client); 468 | readfile: 469 | while(argByte != 0x0D){ 470 | if (!client.connected()) return _parseFormUploadAborted(); 471 | _uploadWriteByte(argByte); 472 | argByte = _uploadReadByte(client); 473 | } 474 | 475 | argByte = _uploadReadByte(client); 476 | if (!client.connected()) return _parseFormUploadAborted(); 477 | if (argByte == 0x0A){ 478 | argByte = _uploadReadByte(client); 479 | if (!client.connected()) return _parseFormUploadAborted(); 480 | if ((char)argByte != '-'){ 481 | //continue reading the file 482 | _uploadWriteByte(0x0D); 483 | _uploadWriteByte(0x0A); 484 | goto readfile; 485 | } else { 486 | argByte = _uploadReadByte(client); 487 | if (!client.connected()) return _parseFormUploadAborted(); 488 | if ((char)argByte != '-'){ 489 | //continue reading the file 490 | _uploadWriteByte(0x0D); 491 | _uploadWriteByte(0x0A); 492 | _uploadWriteByte((uint8_t)('-')); 493 | goto readfile; 494 | } 495 | } 496 | 497 | uint8_t endBuf[boundary.length()]; 498 | client.readBytes(endBuf, boundary.length()); 499 | 500 | if (strstr((const char*)endBuf, boundary.c_str()) != NULL){ 501 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 502 | _currentHandler->upload(*this, _currentUri, _currentUpload); 503 | _currentUpload.totalSize += _currentUpload.currentSize; 504 | _currentUpload.status = UPLOAD_FILE_END; 505 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 506 | _currentHandler->upload(*this, _currentUri, _currentUpload); 507 | #ifdef DEBUG_ESP_HTTP_SERVER 508 | DEBUG_OUTPUT.print("End File: "); 509 | DEBUG_OUTPUT.print(_currentUpload.filename); 510 | DEBUG_OUTPUT.print(" Type: "); 511 | DEBUG_OUTPUT.print(_currentUpload.type); 512 | DEBUG_OUTPUT.print(" Size: "); 513 | DEBUG_OUTPUT.println(_currentUpload.totalSize); 514 | #endif 515 | line = client.readStringUntil(0x0D); 516 | client.readStringUntil(0x0A); 517 | if (line == "--"){ 518 | #ifdef DEBUG_ESP_HTTP_SERVER 519 | DEBUG_OUTPUT.println("Done Parsing POST"); 520 | #endif 521 | break; 522 | } 523 | continue; 524 | } else { 525 | _uploadWriteByte(0x0D); 526 | _uploadWriteByte(0x0A); 527 | _uploadWriteByte((uint8_t)('-')); 528 | _uploadWriteByte((uint8_t)('-')); 529 | uint32_t i = 0; 530 | while(i < boundary.length()){ 531 | _uploadWriteByte(endBuf[i++]); 532 | } 533 | argByte = _uploadReadByte(client); 534 | goto readfile; 535 | } 536 | } else { 537 | _uploadWriteByte(0x0D); 538 | goto readfile; 539 | } 540 | break; 541 | } 542 | } 543 | } 544 | } 545 | 546 | int iarg; 547 | int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount; 548 | for (iarg = 0; iarg < totalArgs; iarg++){ 549 | RequestArgument& arg = postArgs[postArgsLen++]; 550 | arg.key = _currentArgs[iarg].key; 551 | arg.value = _currentArgs[iarg].value; 552 | } 553 | if (_currentArgs) delete[] _currentArgs; 554 | _currentArgs = new RequestArgument[postArgsLen]; 555 | for (iarg = 0; iarg < postArgsLen; iarg++){ 556 | RequestArgument& arg = _currentArgs[iarg]; 557 | arg.key = postArgs[iarg].key; 558 | arg.value = postArgs[iarg].value; 559 | } 560 | _currentArgCount = iarg; 561 | if (postArgs) delete[] postArgs; 562 | return true; 563 | } 564 | #ifdef DEBUG_ESP_HTTP_SERVER 565 | DEBUG_OUTPUT.print("Error: line: "); 566 | DEBUG_OUTPUT.println(line); 567 | #endif 568 | return false; 569 | } 570 | 571 | String WebServer::urlDecode(const String& text) 572 | { 573 | String decoded = ""; 574 | char temp[] = "0x00"; 575 | unsigned int len = text.length(); 576 | unsigned int i = 0; 577 | while (i < len) 578 | { 579 | char decodedChar; 580 | char encodedChar = text.charAt(i++); 581 | if ((encodedChar == '%') && (i + 1 < len)) 582 | { 583 | temp[2] = text.charAt(i++); 584 | temp[3] = text.charAt(i++); 585 | 586 | decodedChar = strtol(temp, NULL, 16); 587 | } 588 | else { 589 | if (encodedChar == '+') 590 | { 591 | decodedChar = ' '; 592 | } 593 | else { 594 | decodedChar = encodedChar; // normal ascii char 595 | } 596 | } 597 | decoded += decodedChar; 598 | } 599 | return decoded; 600 | } 601 | 602 | bool WebServer::_parseFormUploadAborted(){ 603 | _currentUpload.status = UPLOAD_FILE_ABORTED; 604 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 605 | _currentHandler->upload(*this, _currentUri, _currentUpload); 606 | return false; 607 | } 608 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

SDL_ESP32_BC24_GETIP
2 |
3 | SwitchDoc Labs

4 | Start: June 2018
5 | www.switchdoc.labs
6 |
7 |
8 | 9 | This software library is designed to provide a flexible way of provisioning WiFiconnections in a system and router flexible way. 10 | 11 | It consists of three different methods:
12 | - SmartConfig
13 | - WPS Button on AP
14 | - Local AP on ESP32 (192.168.4.1)
15 | 16 | Note: This was developed for the BC24 ESP32 Based 24 RGBW Pixel LED Board
17 | 18 | In order to install the Arduino IDE with support for the ESP32 on the BC24, please follow the tutorial here: 19 | 20 | http://www.switchdoc.com/2018/07/tutorial-arduino-ide-esp32-bc24/ 21 | 22 | Select the Adafruit ESP32 Feather under Tools 23 | 24 | If you get a "\SDL_ESP32_BC24DEMO\SDL_ESP32_BC24DEMO.ino:69:21: fatal error: TimeLib.h: No such file or directory" 25 | 26 | Go to this link github.com/PaulStoffregen/Time and download the .zip file. Then, in the IDE, go to Sketch>Include Library and click on Add .ZIP Library... In your file download area, you should be able to find the Time-Master.zip. Click on it and Open. It will install the Time.h required for the compilation of the sketch. Try to compile. If you get a repeat error, ,then close the IDE and restart it. Then re-compiling should work. 27 | 28 | Plug a USB Micro to USB connector into your computer (the one with the power supply will work) to establish the connection. 29 | 30 |

31 | SmartConfig
32 |

33 | This method uses the TI method of provisiong 34 | "SmartConfig" was invented by TI. You can refer to it here:
35 | https://community.particle.io/t/smart-config-the-missing-manual-now-available/442
36 | In order to do SmartConfig, you need a smartphone or tablet (Android or iOS) that connected to WiFi network (which you want ESP32 to connect to) and installed a special application. 37 | On this application, you just supply the ssid and password of WiFi network so that the application can use, encode them and then broadcast (via UDP) encoded ssid and password (under packet format) over the air. 38 | When this software is being run, the ESP32 (with the SmartConfig software loaded) will capture these packets, decode back ssid and password and use them to connect to Wifi network. 39 | 40 | After connecting to WiFi, the ESP32 will use mDNS to multicast a message to the application to notify that it has successfully connected to WiFi. 41 | 42 | The source code of special application is supplied by Espressif. You can download at:
43 | https://github.com/EspressifApp/EsptouchForAndroid
44 | https://github.com/EspressifApp/EsptouchForIOS
45 | This application is also available on App Store. You can use it to test SmartConfig feature.
46 | - For Android, this application is available under name "IOT_Espressif" or another application "ESP8266 SmartConfig" (this is for ESP8266 but you can use it for ESP32):
47 | 48 | https://play.google.com/store/apps/details?id=com.cmmakerclub.iot.esptouch
49 | https://play.google.com/store/apps/details?id=com.espressif.iot
50 |

51 | - For iOS, this application is available under name "Espressif Esptouch":
52 | https://itunes.apple.com/us/app/espressif-esptouch/id1071176700?mt=8
53 | 54 | There is also another app on the iOS Appstore, search on "SmartConfig"
55 |
56 |

57 | WPS Button on AP
58 |

59 | Wi-Fi Protected Setup (WPS; originally, Wi-Fi Simple Config) is a network security standard to create a secure wireless home network. 60 | 61 | Introduced in 2006, the goal of the protocol is to allow home users who know little of wireless security and may be intimidated by the available security options to set up Wi-Fi Protected Access, as well as making it easy to add new devices to an existing network without entering long passphrases. 62 | 63 | This library will wait 60 seconds (in the example) for the WPS packets to be recieved by the ESP32. 64 | 65 | A major security flaw was revealed in December 2011 that affects wireless routers with the WPS PIN feature, which most recent models have enabled by default. 66 | 67 | This software does not use the PIN feature. 68 | 69 |

70 | Local AP (192.168.4.1)
71 |

72 | 73 | 74 | For the third provisioning method, the ESP32 is set up as an access point (192.168.4.1) - look at your list of WiFi APs on your computer when it is running. A small web server is started that will allow you to select the AP that you want the ESP32 to connect to and then you can enter the password for the access point. 75 | It runs for 60 seconds by default. 76 | 77 |

78 | Notes
79 |

80 | 81 | The two general defines in the example are:
82 | - BC24:
83 | Define BC24 if you are using a BC24 ESP32 based device. THere are visual clues for what the ESP32 is doing while provisioning. See the archive at:
84 | https://github.com/switchdoclabs/SDL_ESP32_BC24NEO 85 | 86 | - BC24DEBUG:
87 | Define BC24DEBUG (by default in the example it is defined) to see lots of debugging information which can help resolve problems and issues. 88 | Remember that all of these features may fail and have to be repeated. Nature of the beast. 89 | 90 | See how to use all three in the SDL_ESP32_BC24_GETIP.ino file. 91 | -------------------------------------------------------------------------------- /SDL_ESP32_BC24_GETIP.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDL_ESP_BC24_GETIP 3 | // Collection of routines to get IP from AP Access Point 4 | // SwitchDoc 5 | // June 2018 Version 1.0 6 | // 7 | // 8 | // 1) SmartConfig 9 | // 3) WPS Button 10 | // 4) Local AP 11 | 12 | // definitions 13 | 14 | // BC24 - define if you have a BC24 (that has an ESP32 on board) 15 | // BC24DEBUG - define if you want debugging output 16 | 17 | 18 | void WiFiEvent(WiFiEvent_t event, system_event_info_t info); 19 | 20 | int WPSReconnectCount = 0; // for timeout on WPS 21 | 22 | // SmartConfig 23 | 24 | bool SmartConfigGetIP(long waitForStartSeconds, long secondsToWaitAfterStart) 25 | { 26 | 27 | bool myWiFiPresent; 28 | myWiFiPresent = false; 29 | 30 | // setup WiFi 31 | //Init WiFi as Station, start SmartConfig 32 | esp_err_t error_code = 0; 33 | 34 | 35 | WiFi.mode(WIFI_AP_STA); 36 | #ifdef BC24DEBUG 37 | Serial.print("error_code="); 38 | Serial.println(error_code); 39 | #endif 40 | WiFi.beginSmartConfig(); 41 | 42 | //Wait for SmartConfig packet from mobile 43 | Serial.println("Waiting for SmartConfig."); 44 | 45 | for (int i = 0; i < waitForStartSeconds; i++) 46 | { 47 | if (WiFi.smartConfigDone()) 48 | { 49 | 50 | Serial.println(""); 51 | Serial.println("SmartConfig received."); 52 | //Wait for WiFi to connect to AP 53 | Serial.println("Waiting for SmartConfig WiFi Start"); 54 | 55 | for (int i = 0; i < secondsToWaitAfterStart ; i++) 56 | { 57 | if (WiFi.status() == WL_CONNECTED) 58 | { 59 | 60 | Serial.println(""); 61 | Serial.println("WiFI Connected."); 62 | myWiFiPresent = true; 63 | #ifdef BC24 64 | BC24ThreeBlink(Green, 1000); 65 | #else 66 | vTaskDelay(1000 / portTICK_PERIOD_MS); 67 | #endif 68 | break; 69 | } 70 | 71 | delay(500); 72 | Serial.print("."); 73 | myWiFiPresent = false; 74 | #ifdef BC24 75 | BC24ThreeBlink(White, 1000); 76 | #else 77 | vTaskDelay(1000 / portTICK_PERIOD_MS); 78 | #endif 79 | } 80 | Serial.println(); 81 | break; 82 | } 83 | //delay(500); 84 | Serial.print("."); 85 | #ifdef BC24 86 | BC24ThreeBlink(Blue, 1000); 87 | #else 88 | vTaskDelay(1000 / portTICK_PERIOD_MS); 89 | #endif 90 | } 91 | if (myWiFiPresent == false) 92 | { 93 | esp_smartconfig_stop(); 94 | 95 | Serial.println("No Wifi Present from Smart Config"); 96 | } 97 | else 98 | { 99 | Serial.println("Wifi Present from Smart Config"); 100 | } 101 | 102 | #ifdef BC24 103 | BC24ThreeBlink(Red, 1000); 104 | #else 105 | vTaskDelay(1000 / portTICK_PERIOD_MS); 106 | #endif 107 | return myWiFiPresent; 108 | } 109 | 110 | 111 | // 112 | // WPS Button GET IP 113 | // 114 | bool WPSGetIP(long timeOutSeconds) 115 | { 116 | 117 | 118 | bool myWiFiPresent; 119 | myWiFiPresent = false; 120 | // set timeout 121 | 122 | esp_err_t error_code = 0; 123 | 124 | Serial.println("Now Try WPS Button"); 125 | 126 | WiFi.disconnect(); 127 | // Smart Config Failed - now try WPS Button 128 | #ifdef BC24 129 | BC24ThreeBlink(White, 1000); 130 | #else 131 | vTaskDelay(1000 / portTICK_PERIOD_MS); 132 | #endif 133 | long timeout; 134 | timeout = millis() + timeOutSeconds * 1000; 135 | 136 | WiFi.onEvent(WiFiEvent); 137 | 138 | WiFi.mode(WIFI_MODE_STA); 139 | Serial.print("error_code="); 140 | Serial.println(error_code); 141 | 142 | 143 | Serial.println("Starting WPS"); 144 | #ifdef BC24 145 | BC24TwoBlink(White, 1000); 146 | #else 147 | vTaskDelay(1000 / portTICK_PERIOD_MS); 148 | #endif 149 | error_code = esp_wifi_wps_enable(&config); 150 | #ifdef BC24DEBUG 151 | Serial.print("error_code="); 152 | Serial.println(error_code, HEX); 153 | #endif 154 | 155 | error_code = esp_wifi_wps_start(0); 156 | #ifdef BC24DEBUG 157 | Serial.print("error_code="); 158 | Serial.println(error_code, HEX); 159 | #endif 160 | // attempt to connect to Wifi network: 161 | while (WiFi.status() != WL_CONNECTED) { 162 | // Connect to WPA/WPA2 network. Change this line if using open or WEP network: 163 | #ifdef BC24 164 | BC24TwoBlink(White, 1000); 165 | #else 166 | vTaskDelay(1000 / portTICK_PERIOD_MS); 167 | #endif 168 | Serial.print("."); 169 | if (timeout < millis()) 170 | { 171 | break; 172 | } 173 | 174 | } 175 | if (WiFi.status() != WL_CONNECTED) 176 | { 177 | esp_wifi_wps_disable(); 178 | Serial.println("WPS Failure"); 179 | myWiFiPresent = false; 180 | #ifdef BC24 181 | BC24TwoBlink(Red, 1000); 182 | #else 183 | vTaskDelay(1000 / portTICK_PERIOD_MS); 184 | #endif 185 | 186 | } 187 | else 188 | { 189 | myWiFiPresent = true; 190 | Serial.println("WPS Success - WiFi connected"); 191 | Serial.println("IP address: "); 192 | Serial.println(WiFi.localIP()); 193 | #ifdef BC24 194 | BC24TwoBlink(Green, 1000); 195 | #else 196 | vTaskDelay(1000 / portTICK_PERIOD_MS); 197 | #endif 198 | } 199 | 200 | return myWiFiPresent; 201 | } 202 | 203 | 204 | // 205 | // Use Local AP (192.168.4.1) 206 | // 207 | 208 | 209 | bool localAPGetIP(long apTimeOutSeconds) 210 | { 211 | 212 | bool myWiFiPresent; 213 | myWiFiPresent = false; 214 | 215 | // set up AP point for reading ssid/password since SmartConfig didn't work 216 | // Set up Wifi 217 | 218 | 219 | #define WL_MAC_ADDR_LENGTH 6 220 | // Append the last two bytes of the MAC (HEX'd) to string to make unique 221 | uint8_t mac[WL_MAC_ADDR_LENGTH]; 222 | WiFi.softAPmacAddress(mac); 223 | String macID = String(mac[WL_MAC_ADDR_LENGTH - 2], HEX) + 224 | String(mac[WL_MAC_ADDR_LENGTH - 1], HEX); 225 | macID.toUpperCase(); 226 | APssid = "BigCircle24-" + macID; 227 | 228 | 229 | //WiFiManager 230 | //Local intialization. Once its business is done, there is no need to keep it around 231 | WiFiManager wifiManager; 232 | #ifdef BC24DEBUG 233 | wifiManager.setDebugOutput(true); 234 | #else 235 | wifiManager.setDebugOutput(false); 236 | #endif 237 | //reset saved settings 238 | //wifiManager.resetSettings(); 239 | #ifdef BC24 240 | BC24OneBlink(White, 1000); 241 | 242 | Serial.println("Before Semaphore Give"); 243 | xSemaphoreGive( xSemaphoreSingleBlink); // Turn on single blink 244 | Serial.println("After Semaphore Give"); 245 | #else 246 | vTaskDelay(1000 / portTICK_PERIOD_MS); 247 | #endif 248 | //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode 249 | wifiManager.setAPCallback(configModeCallback); 250 | //fetches ssid and pass and tries to connect 251 | //if it does not connect it starts an access point with the specified name 252 | wifiManager.setTimeout(apTimeOutSeconds); 253 | //and goes into a blocking loop awaiting configuration 254 | if (!wifiManager.autoConnect(APssid.c_str())) { 255 | Serial.println("failed to connect and hit timeout"); 256 | #ifdef BC24 257 | blinkLED(4, 300); // blink 4, failed to connect 258 | BC24OneBlink(Red, 1000); 259 | #else 260 | vTaskDelay(1000 / portTICK_PERIOD_MS); 261 | #endif 262 | //reset and try again, or maybe put it to deep sleep 263 | //ESP.reset(); 264 | //delay(1000); 265 | myWiFiPresent = false; 266 | } 267 | #ifdef BC24 268 | xSemaphoreTake( xSemaphoreSingleBlink, 10); // Turn off single blink 269 | #endif 270 | if (WiFi.status() == WL_CONNECTED) 271 | { 272 | myWiFiPresent = true; 273 | #ifdef BC24 274 | BC24OneBlink(Green, 1000); 275 | #else 276 | vTaskDelay(1000 / portTICK_PERIOD_MS); 277 | #endif 278 | } 279 | 280 | 281 | return myWiFiPresent; 282 | } 283 | 284 | 285 | // for WPS 286 | void WiFiEvent(WiFiEvent_t event, system_event_info_t info) { 287 | switch (event) { 288 | case SYSTEM_EVENT_STA_START: 289 | Serial.println("Station Mode Started"); 290 | break; 291 | case SYSTEM_EVENT_STA_GOT_IP: 292 | Serial.println("Connected to :" + String(WiFi.SSID())); 293 | Serial.print("Got IP: "); 294 | Serial.println(WiFi.localIP()); 295 | break; 296 | case SYSTEM_EVENT_STA_DISCONNECTED: 297 | Serial.println("Disconnected from station, attempting reconnection"); 298 | if (WPSReconnectCount < 10) 299 | { 300 | WiFi.reconnect(); 301 | WPSReconnectCount++; 302 | 303 | } 304 | esp_wifi_wps_disable(); 305 | delay(10); 306 | break; 307 | case SYSTEM_EVENT_STA_WPS_ER_SUCCESS: 308 | Serial.println("WPS Successful, stopping WPS and connecting to: " + String(WiFi.SSID())); 309 | esp_wifi_wps_disable(); 310 | delay(10); 311 | WiFi.begin(); 312 | break; 313 | case SYSTEM_EVENT_STA_WPS_ER_FAILED: 314 | Serial.println("WPS Failed"); 315 | esp_wifi_wps_disable(); 316 | //esp_wifi_wps_enable(&config); 317 | //esp_wifi_wps_start(0); 318 | break; 319 | case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT: 320 | Serial.println("WPS Timeout"); 321 | esp_wifi_wps_disable(); 322 | //esp_wifi_wps_enable(&config); 323 | //esp_wifi_wps_start(0); 324 | break; 325 | 326 | default: 327 | break; 328 | } 329 | } 330 | 331 | -------------------------------------------------------------------------------- /SDL_ESP32_BC24_GETIP.ino: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // SDL_ESP32_BC24_GETIP Demo Program 4 | // SwitchDoc Labs 5 | // 6 | // June 2018 7 | // 8 | 9 | 10 | #if defined(ARDUINO) && ARDUINO >= 100 11 | // No extras 12 | #elif defined(ARDUINO) // pre-1.0 13 | // No extras 14 | #elif defined(ESP_PLATFORM) 15 | #include "arduinoish.hpp" 16 | #endif 17 | 18 | #define BC24DEBUG 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #define ESP_WPS_MODE WPS_TYPE_PBC 26 | 27 | esp_wps_config_t config = WPS_CONFIG_INIT_DEFAULT(ESP_WPS_MODE); 28 | 29 | 30 | #include 31 | #include 32 | 33 | // AP Variables 34 | 35 | #include "WiFiManager.h" //https://github.com/tzapu/WiFiManager 36 | 37 | //gets called when WiFiManager enters configuration mode 38 | 39 | 40 | void configModeCallback (WiFiManager *myWiFiManager) 41 | //void configModeCallback () 42 | { 43 | 44 | Serial.println("Entered config mode"); 45 | 46 | Serial.println(WiFi.softAPIP()); 47 | 48 | } 49 | 50 | #define WEB_SERVER_PORT 80 51 | String APssid; 52 | 53 | String Wssid; 54 | String WPassword; 55 | 56 | WiFiServer server(WEB_SERVER_PORT); 57 | 58 | // include GET IP routines 59 | #include "SDL_ESP32_BC24_GETIP.h" 60 | 61 | 62 | String WiFi_SSID = ""; 63 | String WiFi_psk = ""; 64 | 65 | 66 | 67 | bool WiFiPresent = false; 68 | 69 | void setup() { 70 | 71 | Serial.begin(115200); 72 | 73 | Serial.println(); 74 | Serial.println(); 75 | Serial.println("--------------------"); 76 | Serial.println("WiFi Provisioning ESP32 Software Demo"); 77 | Serial.println("--------------------"); 78 | Serial.print("Version: 1.0"); 79 | 80 | 81 | Serial.print("Compiled at:"); 82 | Serial.print (__TIME__); 83 | Serial.print(" "); 84 | Serial.println(__DATE__); 85 | 86 | WiFiPresent = false; 87 | 88 | if (WiFiPresent == false) 89 | { 90 | // do SmartConfig 91 | #define WAITFORSTART 15 92 | #define WAITFORCONNECT 20 93 | 94 | WiFiPresent = SmartConfigGetIP(WAITFORSTART, WAITFORCONNECT); 95 | 96 | } // if not already programmed before 97 | 98 | 99 | 100 | 101 | 102 | if (WiFiPresent != true) 103 | { 104 | #define WPSTIMEOUTSECONDS 60 105 | // now try WPS Button 106 | 107 | WiFiPresent = WPSGetIP(WPSTIMEOUTSECONDS); 108 | 109 | } 110 | 111 | if (WiFiPresent != true) 112 | { 113 | #define APTIMEOUTSECONDS 60 114 | WiFiPresent = localAPGetIP(APTIMEOUTSECONDS); 115 | } 116 | 117 | 118 | if (WiFiPresent == true) 119 | { 120 | 121 | 122 | Serial.println("-------------"); 123 | Serial.println("WiFi Connected"); 124 | Serial.println("-------------"); 125 | WiFi_SSID = WiFi.SSID(); 126 | WiFi_psk = WiFi.psk(); 127 | Serial.print("SSID="); 128 | Serial.println(WiFi_SSID); 129 | 130 | Serial.print("psk="); 131 | Serial.println(WiFi_psk); 132 | } 133 | else 134 | { 135 | Serial.println("-------------"); 136 | Serial.println("WiFi NOT Connected"); 137 | Serial.println("-------------"); 138 | } 139 | 140 | } 141 | 142 | void loop() { 143 | // put your main code here, to run repeatedly: 144 | 145 | } 146 | -------------------------------------------------------------------------------- /WebServer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | WebServer.cpp - Dead simple web-server. 3 | Supports only one simultaneous client, knows how to handle GET and POST. 4 | 5 | Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) 21 | */ 22 | 23 | 24 | #include 25 | #include 26 | #include "WiFiServer.h" 27 | #include "WiFiClient.h" 28 | #include "WebServer.h" 29 | #include "FS.h" 30 | #include "detail/RequestHandlersImpl.h" 31 | 32 | //#define DEBUG_ESP_HTTP_SERVER 33 | #ifdef DEBUG_ESP_PORT 34 | #define DEBUG_OUTPUT DEBUG_ESP_PORT 35 | #else 36 | #define DEBUG_OUTPUT Serial 37 | #endif 38 | 39 | const char * AUTHORIZATION_HEADER = "Authorization"; 40 | 41 | WebServer::WebServer(IPAddress addr, int port) 42 | : _server(addr, port) 43 | , _currentMethod(HTTP_ANY) 44 | , _currentVersion(0) 45 | , _currentStatus(HC_NONE) 46 | , _statusChange(0) 47 | , _currentHandler(0) 48 | , _firstHandler(0) 49 | , _lastHandler(0) 50 | , _currentArgCount(0) 51 | , _currentArgs(0) 52 | , _headerKeysCount(0) 53 | , _currentHeaders(0) 54 | , _contentLength(0) 55 | , _chunked(false) 56 | { 57 | } 58 | 59 | WebServer::WebServer(int port) 60 | : _server(port) 61 | , _currentMethod(HTTP_ANY) 62 | , _currentVersion(0) 63 | , _currentStatus(HC_NONE) 64 | , _statusChange(0) 65 | , _currentHandler(0) 66 | , _firstHandler(0) 67 | , _lastHandler(0) 68 | , _currentArgCount(0) 69 | , _currentArgs(0) 70 | , _headerKeysCount(0) 71 | , _currentHeaders(0) 72 | , _contentLength(0) 73 | , _chunked(false) 74 | { 75 | } 76 | 77 | WebServer::~WebServer() { 78 | if (_currentHeaders) 79 | delete[]_currentHeaders; 80 | _headerKeysCount = 0; 81 | RequestHandler* handler = _firstHandler; 82 | while (handler) { 83 | RequestHandler* next = handler->next(); 84 | delete handler; 85 | handler = next; 86 | } 87 | close(); 88 | } 89 | 90 | void WebServer::begin() { 91 | _currentStatus = HC_NONE; 92 | _server.begin(); 93 | if(!_headerKeysCount) 94 | collectHeaders(0, 0); 95 | } 96 | 97 | bool WebServer::authenticate(const char * username, const char * password){ 98 | if(hasHeader(AUTHORIZATION_HEADER)){ 99 | String authReq = header(AUTHORIZATION_HEADER); 100 | if(authReq.startsWith("Basic")){ 101 | authReq = authReq.substring(6); 102 | authReq.trim(); 103 | char toencodeLen = strlen(username)+strlen(password)+1; 104 | char *toencode = new char[toencodeLen + 1]; 105 | if(toencode == NULL){ 106 | authReq = String(); 107 | return false; 108 | } 109 | char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; 110 | if(encoded == NULL){ 111 | authReq = String(); 112 | delete[] toencode; 113 | return false; 114 | } 115 | sprintf(toencode, "%s:%s", username, password); 116 | if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){ 117 | authReq = String(); 118 | delete[] toencode; 119 | delete[] encoded; 120 | return true; 121 | } 122 | delete[] toencode; 123 | delete[] encoded; 124 | } 125 | authReq = String(); 126 | } 127 | return false; 128 | } 129 | 130 | void WebServer::requestAuthentication(){ 131 | sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); 132 | send(401); 133 | } 134 | 135 | void WebServer::on(const String &uri, WebServer::THandlerFunction handler) { 136 | on(uri, HTTP_ANY, handler); 137 | } 138 | 139 | void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn) { 140 | on(uri, method, fn, _fileUploadHandler); 141 | } 142 | 143 | void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) { 144 | _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method)); 145 | } 146 | 147 | void WebServer::addHandler(RequestHandler* handler) { 148 | _addRequestHandler(handler); 149 | } 150 | 151 | void WebServer::_addRequestHandler(RequestHandler* handler) { 152 | if (!_lastHandler) { 153 | _firstHandler = handler; 154 | _lastHandler = handler; 155 | } 156 | else { 157 | _lastHandler->next(handler); 158 | _lastHandler = handler; 159 | } 160 | } 161 | 162 | void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) { 163 | _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header)); 164 | } 165 | 166 | void WebServer::handleClient() { 167 | if (_currentStatus == HC_NONE) { 168 | WiFiClient client = _server.available(); 169 | if (!client) { 170 | return; 171 | } 172 | 173 | #ifdef DEBUG_ESP_HTTP_SERVER 174 | DEBUG_OUTPUT.println("New client"); 175 | #endif 176 | 177 | _currentClient = client; 178 | _currentStatus = HC_WAIT_READ; 179 | _statusChange = millis(); 180 | } 181 | 182 | if (!_currentClient.connected()) { 183 | _currentClient = WiFiClient(); 184 | _currentStatus = HC_NONE; 185 | return; 186 | } 187 | 188 | // Wait for data from client to become available 189 | if (_currentStatus == HC_WAIT_READ) { 190 | if (!_currentClient.available()) { 191 | if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) { 192 | _currentClient = WiFiClient(); 193 | _currentStatus = HC_NONE; 194 | } 195 | yield(); 196 | return; 197 | } 198 | 199 | if (!_parseRequest(_currentClient)) { 200 | _currentClient = WiFiClient(); 201 | _currentStatus = HC_NONE; 202 | return; 203 | } 204 | _currentClient.setTimeout(HTTP_MAX_SEND_WAIT); 205 | _contentLength = CONTENT_LENGTH_NOT_SET; 206 | _handleRequest(); 207 | 208 | if (!_currentClient.connected()) { 209 | _currentClient = WiFiClient(); 210 | _currentStatus = HC_NONE; 211 | return; 212 | } else { 213 | _currentStatus = HC_WAIT_CLOSE; 214 | _statusChange = millis(); 215 | return; 216 | } 217 | } 218 | 219 | if (_currentStatus == HC_WAIT_CLOSE) { 220 | if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) { 221 | _currentClient = WiFiClient(); 222 | _currentStatus = HC_NONE; 223 | } else { 224 | yield(); 225 | return; 226 | } 227 | } 228 | } 229 | 230 | void WebServer::close() { 231 | #ifdef ESP8266 232 | _server.stop(); 233 | #else 234 | // TODO add ESP32 WiFiServer::stop() 235 | _server.end(); 236 | #endif 237 | } 238 | 239 | void WebServer::stop() { 240 | close(); 241 | } 242 | 243 | void WebServer::sendHeader(const String& name, const String& value, bool first) { 244 | String headerLine = name; 245 | headerLine += ": "; 246 | headerLine += value; 247 | headerLine += "\r\n"; 248 | 249 | if (first) { 250 | _responseHeaders = headerLine + _responseHeaders; 251 | } 252 | else { 253 | _responseHeaders += headerLine; 254 | } 255 | } 256 | 257 | void WebServer::setContentLength(size_t contentLength) { 258 | _contentLength = contentLength; 259 | } 260 | 261 | void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) { 262 | response = "HTTP/1."+String(_currentVersion)+" "; 263 | response += String(code); 264 | response += " "; 265 | response += _responseCodeToString(code); 266 | response += "\r\n"; 267 | 268 | if (!content_type) 269 | content_type = "text/html"; 270 | 271 | sendHeader("Content-Type", content_type, true); 272 | if (_contentLength == CONTENT_LENGTH_NOT_SET) { 273 | sendHeader("Content-Length", String(contentLength)); 274 | } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { 275 | sendHeader("Content-Length", String(_contentLength)); 276 | } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client 277 | //let's do chunked 278 | _chunked = true; 279 | sendHeader("Accept-Ranges","none"); 280 | sendHeader("Transfer-Encoding","chunked"); 281 | } 282 | sendHeader("Connection", "close"); 283 | 284 | response += _responseHeaders; 285 | response += "\r\n"; 286 | _responseHeaders = String(); 287 | } 288 | 289 | void WebServer::send(int code, const char* content_type, const String& content) { 290 | String header; 291 | // Can we asume the following? 292 | //if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET) 293 | // _contentLength = CONTENT_LENGTH_UNKNOWN; 294 | _prepareHeader(header, code, content_type, content.length()); 295 | _currentClient.write(header.c_str(), header.length()); 296 | if(content.length()) 297 | sendContent(content); 298 | } 299 | 300 | void WebServer::send_P(int code, PGM_P content_type, PGM_P content) { 301 | size_t contentLength = 0; 302 | 303 | if (content != NULL) { 304 | contentLength = strlen_P(content); 305 | } 306 | 307 | String header; 308 | char type[64]; 309 | memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type)); 310 | _prepareHeader(header, code, (const char* )type, contentLength); 311 | _currentClient.write(header.c_str(), header.length()); 312 | sendContent_P(content); 313 | } 314 | 315 | void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) { 316 | String header; 317 | char type[64]; 318 | memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type)); 319 | _prepareHeader(header, code, (const char* )type, contentLength); 320 | sendContent(header); 321 | sendContent_P(content, contentLength); 322 | } 323 | 324 | void WebServer::send(int code, char* content_type, const String& content) { 325 | send(code, (const char*)content_type, content); 326 | } 327 | 328 | void WebServer::send(int code, const String& content_type, const String& content) { 329 | send(code, (const char*)content_type.c_str(), content); 330 | } 331 | 332 | void WebServer::sendContent(const String& content) { 333 | const char * footer = "\r\n"; 334 | size_t len = content.length(); 335 | if(_chunked) { 336 | char * chunkSize = (char *)malloc(11); 337 | if(chunkSize){ 338 | sprintf(chunkSize, "%x%s", len, footer); 339 | _currentClient.write(chunkSize, strlen(chunkSize)); 340 | free(chunkSize); 341 | } 342 | } 343 | _currentClient.write(content.c_str(), len); 344 | if(_chunked){ 345 | _currentClient.write(footer, 2); 346 | } 347 | } 348 | 349 | void WebServer::sendContent_P(PGM_P content) { 350 | sendContent_P(content, strlen_P(content)); 351 | } 352 | 353 | void WebServer::sendContent_P(PGM_P content, size_t size) { 354 | const char * footer = "\r\n"; 355 | if(_chunked) { 356 | char * chunkSize = (char *)malloc(11); 357 | if(chunkSize){ 358 | sprintf(chunkSize, "%x%s", size, footer); 359 | _currentClient.write(chunkSize, strlen(chunkSize)); 360 | free(chunkSize); 361 | } 362 | } 363 | _currentClient.write_P(content, size); 364 | if(_chunked){ 365 | _currentClient.write(footer, 2); 366 | } 367 | } 368 | 369 | 370 | String WebServer::arg(String name) { 371 | for (int i = 0; i < _currentArgCount; ++i) { 372 | if ( _currentArgs[i].key == name ) 373 | return _currentArgs[i].value; 374 | } 375 | return String(); 376 | } 377 | 378 | String WebServer::arg(int i) { 379 | if (i < _currentArgCount) 380 | return _currentArgs[i].value; 381 | return String(); 382 | } 383 | 384 | String WebServer::argName(int i) { 385 | if (i < _currentArgCount) 386 | return _currentArgs[i].key; 387 | return String(); 388 | } 389 | 390 | int WebServer::args() { 391 | return _currentArgCount; 392 | } 393 | 394 | bool WebServer::hasArg(String name) { 395 | for (int i = 0; i < _currentArgCount; ++i) { 396 | if (_currentArgs[i].key == name) 397 | return true; 398 | } 399 | return false; 400 | } 401 | 402 | 403 | String WebServer::header(String name) { 404 | for (int i = 0; i < _headerKeysCount; ++i) { 405 | if (_currentHeaders[i].key.equalsIgnoreCase(name)) 406 | return _currentHeaders[i].value; 407 | } 408 | return String(); 409 | } 410 | 411 | void WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { 412 | _headerKeysCount = headerKeysCount + 1; 413 | if (_currentHeaders) 414 | delete[]_currentHeaders; 415 | _currentHeaders = new RequestArgument[_headerKeysCount]; 416 | _currentHeaders[0].key = AUTHORIZATION_HEADER; 417 | for (int i = 1; i < _headerKeysCount; i++){ 418 | _currentHeaders[i].key = headerKeys[i-1]; 419 | } 420 | } 421 | 422 | String WebServer::header(int i) { 423 | if (i < _headerKeysCount) 424 | return _currentHeaders[i].value; 425 | return String(); 426 | } 427 | 428 | String WebServer::headerName(int i) { 429 | if (i < _headerKeysCount) 430 | return _currentHeaders[i].key; 431 | return String(); 432 | } 433 | 434 | int WebServer::headers() { 435 | return _headerKeysCount; 436 | } 437 | 438 | bool WebServer::hasHeader(String name) { 439 | for (int i = 0; i < _headerKeysCount; ++i) { 440 | if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0)) 441 | return true; 442 | } 443 | return false; 444 | } 445 | 446 | String WebServer::hostHeader() { 447 | return _hostHeader; 448 | } 449 | 450 | void WebServer::onFileUpload(THandlerFunction fn) { 451 | _fileUploadHandler = fn; 452 | } 453 | 454 | void WebServer::onNotFound(THandlerFunction fn) { 455 | _notFoundHandler = fn; 456 | } 457 | 458 | void WebServer::_handleRequest() { 459 | bool handled = false; 460 | if (!_currentHandler){ 461 | #ifdef DEBUG_ESP_HTTP_SERVER 462 | DEBUG_OUTPUT.println("request handler not found"); 463 | #endif 464 | } 465 | else { 466 | handled = _currentHandler->handle(*this, _currentMethod, _currentUri); 467 | #ifdef DEBUG_ESP_HTTP_SERVER 468 | if (!handled) { 469 | DEBUG_OUTPUT.println("request handler failed to handle request"); 470 | } 471 | #endif 472 | } 473 | 474 | if (!handled) { 475 | if(_notFoundHandler) { 476 | _notFoundHandler(); 477 | } 478 | else { 479 | send(404, "text/plain", String("Not found: ") + _currentUri); 480 | } 481 | } 482 | 483 | _currentUri = String(); 484 | } 485 | 486 | String WebServer::_responseCodeToString(int code) { 487 | switch (code) { 488 | case 100: return F("Continue"); 489 | case 101: return F("Switching Protocols"); 490 | case 200: return F("OK"); 491 | case 201: return F("Created"); 492 | case 202: return F("Accepted"); 493 | case 203: return F("Non-Authoritative Information"); 494 | case 204: return F("No Content"); 495 | case 205: return F("Reset Content"); 496 | case 206: return F("Partial Content"); 497 | case 300: return F("Multiple Choices"); 498 | case 301: return F("Moved Permanently"); 499 | case 302: return F("Found"); 500 | case 303: return F("See Other"); 501 | case 304: return F("Not Modified"); 502 | case 305: return F("Use Proxy"); 503 | case 307: return F("Temporary Redirect"); 504 | case 400: return F("Bad Request"); 505 | case 401: return F("Unauthorized"); 506 | case 402: return F("Payment Required"); 507 | case 403: return F("Forbidden"); 508 | case 404: return F("Not Found"); 509 | case 405: return F("Method Not Allowed"); 510 | case 406: return F("Not Acceptable"); 511 | case 407: return F("Proxy Authentication Required"); 512 | case 408: return F("Request Time-out"); 513 | case 409: return F("Conflict"); 514 | case 410: return F("Gone"); 515 | case 411: return F("Length Required"); 516 | case 412: return F("Precondition Failed"); 517 | case 413: return F("Request Entity Too Large"); 518 | case 414: return F("Request-URI Too Large"); 519 | case 415: return F("Unsupported Media Type"); 520 | case 416: return F("Requested range not satisfiable"); 521 | case 417: return F("Expectation Failed"); 522 | case 500: return F("Internal Server Error"); 523 | case 501: return F("Not Implemented"); 524 | case 502: return F("Bad Gateway"); 525 | case 503: return F("Service Unavailable"); 526 | case 504: return F("Gateway Time-out"); 527 | case 505: return F("HTTP Version not supported"); 528 | default: return ""; 529 | } 530 | } 531 | -------------------------------------------------------------------------------- /WebServer.h: -------------------------------------------------------------------------------- 1 | /* 2 | WebServer.h - Dead simple web-server. 3 | Supports only one simultaneous client, knows how to handle GET and POST. 4 | 5 | Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) 21 | */ 22 | 23 | 24 | #ifndef WEBSERVER_H 25 | #define WEBSERVER_H 26 | 27 | #include 28 | #ifdef ESP8266 29 | #define WebServer ESP8266WebServer 30 | #include 31 | #else 32 | #include 33 | #define write_P write 34 | #endif 35 | 36 | enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS }; 37 | enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, 38 | UPLOAD_FILE_ABORTED }; 39 | enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE }; 40 | 41 | #define HTTP_DOWNLOAD_UNIT_SIZE 1460 42 | 43 | #ifndef HTTP_UPLOAD_BUFLEN 44 | #define HTTP_UPLOAD_BUFLEN 2048 45 | #endif 46 | 47 | #define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request 48 | #define HTTP_MAX_POST_WAIT 1000 //ms to wait for POST data to arrive 49 | #define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed 50 | #define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection 51 | 52 | #define CONTENT_LENGTH_UNKNOWN ((size_t) -1) 53 | #define CONTENT_LENGTH_NOT_SET ((size_t) -2) 54 | 55 | class WebServer; 56 | 57 | typedef struct { 58 | HTTPUploadStatus status; 59 | String filename; 60 | String name; 61 | String type; 62 | size_t totalSize; // file size 63 | size_t currentSize; // size of data currently in buf 64 | uint8_t buf[HTTP_UPLOAD_BUFLEN]; 65 | } HTTPUpload; 66 | 67 | #include "detail/RequestHandler.h" 68 | 69 | namespace fs { 70 | class FS; 71 | } 72 | 73 | class WebServer 74 | { 75 | public: 76 | WebServer(IPAddress addr, int port = 80); 77 | WebServer(int port = 80); 78 | ~WebServer(); 79 | 80 | void begin(); 81 | void handleClient(); 82 | 83 | void close(); 84 | void stop(); 85 | 86 | bool authenticate(const char * username, const char * password); 87 | void requestAuthentication(); 88 | 89 | typedef std::function THandlerFunction; 90 | void on(const String &uri, THandlerFunction handler); 91 | void on(const String &uri, HTTPMethod method, THandlerFunction fn); 92 | void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); 93 | void addHandler(RequestHandler* handler); 94 | void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL ); 95 | void onNotFound(THandlerFunction fn); //called when handler is not assigned 96 | void onFileUpload(THandlerFunction fn); //handle file uploads 97 | 98 | String uri() { return _currentUri; } 99 | HTTPMethod method() { return _currentMethod; } 100 | WiFiClient client() { return _currentClient; } 101 | HTTPUpload& upload() { return _currentUpload; } 102 | 103 | String arg(String name); // get request argument value by name 104 | String arg(int i); // get request argument value by number 105 | String argName(int i); // get request argument name by number 106 | int args(); // get arguments count 107 | bool hasArg(String name); // check if argument exists 108 | void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect 109 | String header(String name); // get request header value by name 110 | String header(int i); // get request header value by number 111 | String headerName(int i); // get request header name by number 112 | int headers(); // get header count 113 | bool hasHeader(String name); // check if header exists 114 | 115 | String hostHeader(); // get request host header if available or empty String if not 116 | 117 | // send response to the client 118 | // code - HTTP response code, can be 200 or 404 119 | // content_type - HTTP content type, like "text/plain" or "image/png" 120 | // content - actual content body 121 | void send(int code, const char* content_type = NULL, const String& content = String("")); 122 | void send(int code, char* content_type, const String& content); 123 | void send(int code, const String& content_type, const String& content); 124 | void send_P(int code, PGM_P content_type, PGM_P content); 125 | void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength); 126 | 127 | void setContentLength(size_t contentLength); 128 | void sendHeader(const String& name, const String& value, bool first = false); 129 | void sendContent(const String& content); 130 | void sendContent_P(PGM_P content); 131 | void sendContent_P(PGM_P content, size_t size); 132 | 133 | static String urlDecode(const String& text); 134 | 135 | #ifdef ESP8266 136 | template size_t streamFile(T &file, const String& contentType){ 137 | setContentLength(file.size()); 138 | if (String(file.name()).endsWith(".gz") && 139 | contentType != "application/x-gzip" && 140 | contentType != "application/octet-stream"){ 141 | sendHeader("Content-Encoding", "gzip"); 142 | } 143 | send(200, contentType, ""); 144 | return _currentClient.write(file); 145 | } 146 | #else 147 | template size_t streamFile(T &file, const String& contentType){ 148 | #define STREAMFILE_BUFSIZE 2*1460 149 | setContentLength(file.size()); 150 | if (String(file.name()).endsWith(".gz") && 151 | contentType != "application/x-gzip" && 152 | contentType != "application/octet-stream") { 153 | sendHeader("Content-Encoding", "gzip"); 154 | } 155 | send(200, contentType, ""); 156 | uint8_t *buf = (uint8_t *)malloc(STREAMFILE_BUFSIZE); 157 | if (buf == NULL) { 158 | //DBG_OUTPUT_PORT.printf("streamFile malloc failed"); 159 | return 0; 160 | } 161 | size_t totalBytesOut = 0; 162 | while (client().connected() && (file.available() > 0)) { 163 | int bytesOut; 164 | int bytesIn = file.read(buf, STREAMFILE_BUFSIZE); 165 | if (bytesIn <= 0) break; 166 | while (1) { 167 | bytesOut = 0; 168 | if (!client().connected()) break; 169 | bytesOut = client().write(buf, bytesIn); 170 | if (bytesIn == bytesOut) break; 171 | 172 | //DBG_OUTPUT_PORT.printf("bytesIn %d != bytesOut %d\r\n", 173 | //bytesIn, bytesOut); 174 | delay(1); 175 | } 176 | totalBytesOut += bytesOut; 177 | yield(); 178 | } 179 | if (totalBytesOut != file.size()) { 180 | //DBG_OUTPUT_PORT.printf("file size %d bytes out %d\r\n", 181 | // file.size(), totalBytesOut); 182 | } 183 | free(buf); 184 | return totalBytesOut; 185 | } 186 | #endif 187 | 188 | protected: 189 | void _addRequestHandler(RequestHandler* handler); 190 | void _handleRequest(); 191 | bool _parseRequest(WiFiClient& client); 192 | void _parseArguments(String data); 193 | static String _responseCodeToString(int code); 194 | bool _parseForm(WiFiClient& client, String boundary, uint32_t len); 195 | bool _parseFormUploadAborted(); 196 | void _uploadWriteByte(uint8_t b); 197 | uint8_t _uploadReadByte(WiFiClient& client); 198 | void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); 199 | bool _collectHeader(const char* headerName, const char* headerValue); 200 | 201 | struct RequestArgument { 202 | String key; 203 | String value; 204 | }; 205 | 206 | WiFiServer _server; 207 | 208 | WiFiClient _currentClient; 209 | HTTPMethod _currentMethod; 210 | String _currentUri; 211 | uint8_t _currentVersion; 212 | HTTPClientStatus _currentStatus; 213 | unsigned long _statusChange; 214 | 215 | RequestHandler* _currentHandler; 216 | RequestHandler* _firstHandler; 217 | RequestHandler* _lastHandler; 218 | THandlerFunction _notFoundHandler; 219 | THandlerFunction _fileUploadHandler; 220 | 221 | int _currentArgCount; 222 | RequestArgument* _currentArgs; 223 | HTTPUpload _currentUpload; 224 | 225 | int _headerKeysCount; 226 | RequestArgument* _currentHeaders; 227 | size_t _contentLength; 228 | String _responseHeaders; 229 | 230 | String _hostHeader; 231 | bool _chunked; 232 | 233 | }; 234 | 235 | 236 | #endif //WEBSERVER_H 237 | -------------------------------------------------------------------------------- /WiFiManager.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************** 2 | WiFiManager is a library for the ESP8266/Arduino platform 3 | (https://github.com/esp8266/Arduino) to enable easy 4 | configuration and reconfiguration of WiFi credentials using a Captive Portal 5 | inspired by: 6 | http://www.esp8266.com/viewtopic.php?f=29&t=2520 7 | https://github.com/chriscook8/esp-arduino-apboot 8 | https://github.com/esp8266/Arduino/tree/master/libraries/DNSServer/examples/CaptivePortalAdvanced 9 | Built by AlexT https://github.com/tzapu 10 | Licensed under MIT license 11 | **************************************************************/ 12 | 13 | #include "WiFiManager.h" 14 | 15 | 16 | 17 | WiFiManagerParameter::WiFiManagerParameter(const char *custom) { 18 | _id = NULL; 19 | _placeholder = NULL; 20 | _length = 0; 21 | _value = NULL; 22 | 23 | _customHTML = custom; 24 | } 25 | 26 | WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length) { 27 | init(id, placeholder, defaultValue, length, ""); 28 | } 29 | 30 | WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom) { 31 | init(id, placeholder, defaultValue, length, custom); 32 | } 33 | 34 | void WiFiManagerParameter::init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom) { 35 | _id = id; 36 | _placeholder = placeholder; 37 | _length = length; 38 | _value = new char[length + 1]; 39 | for (int i = 0; i < length; i++) { 40 | _value[i] = 0; 41 | } 42 | if (defaultValue != NULL) { 43 | strncpy(_value, defaultValue, length); 44 | } 45 | 46 | _customHTML = custom; 47 | } 48 | 49 | const char* WiFiManagerParameter::getValue() { 50 | return _value; 51 | } 52 | const char* WiFiManagerParameter::getID() { 53 | return _id; 54 | } 55 | const char* WiFiManagerParameter::getPlaceholder() { 56 | return _placeholder; 57 | } 58 | int WiFiManagerParameter::getValueLength() { 59 | return _length; 60 | } 61 | const char* WiFiManagerParameter::getCustomHTML() { 62 | return _customHTML; 63 | } 64 | 65 | WiFiManager::WiFiManager() { 66 | } 67 | 68 | void WiFiManager::addParameter(WiFiManagerParameter *p) { 69 | if (_paramsCount + 1 > WIFI_MANAGER_MAX_PARAMS) 70 | { 71 | //Max parameters exceeded! 72 | DEBUG_WM("WIFI_MANAGER_MAX_PARAMS exceeded, increase number (in WiFiManager.h) before adding more parameters!"); 73 | DEBUG_WM("Skipping parameter with ID:"); 74 | DEBUG_WM(p->getID()); 75 | return; 76 | } 77 | _params[_paramsCount] = p; 78 | _paramsCount++; 79 | DEBUG_WM("Adding parameter"); 80 | DEBUG_WM(p->getID()); 81 | } 82 | 83 | void WiFiManager::setupConfigPortal() { 84 | dnsServer.reset(new DNSServer()); 85 | #ifdef ESP8266 86 | server.reset(new ESP8266WebServer(80)); 87 | #else 88 | server.reset(new WebServer(80)); 89 | #endif 90 | 91 | DEBUG_WM(F("")); 92 | _configPortalStart = millis(); 93 | 94 | DEBUG_WM(F("Configuring access point... ")); 95 | DEBUG_WM(_apName); 96 | if (_apPassword != NULL) { 97 | if (strlen(_apPassword) < 8 || strlen(_apPassword) > 63) { 98 | // fail passphrase to short or long! 99 | DEBUG_WM(F("Invalid AccessPoint password. Ignoring")); 100 | _apPassword = NULL; 101 | } 102 | DEBUG_WM(_apPassword); 103 | } 104 | 105 | //optional soft ip config 106 | if (_ap_static_ip) { 107 | DEBUG_WM(F("Custom AP IP/GW/Subnet")); 108 | WiFi.softAPConfig(_ap_static_ip, _ap_static_gw, _ap_static_sn); 109 | } 110 | 111 | if (_apPassword != NULL) { 112 | WiFi.softAP(_apName, _apPassword);//password option 113 | } else { 114 | WiFi.softAP(_apName); 115 | } 116 | 117 | delay(500); // Without delay I've seen the IP address blank 118 | DEBUG_WM(F("AP IP address: ")); 119 | DEBUG_WM(WiFi.softAPIP()); 120 | 121 | /* Setup the DNS server redirecting all the domains to the apIP */ 122 | dnsServer->setErrorReplyCode(DNSReplyCode::NoError); 123 | dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); 124 | 125 | /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */ 126 | server->on("/", std::bind(&WiFiManager::handleRoot, this)); 127 | server->on("/wifi", std::bind(&WiFiManager::handleWifi, this, true)); 128 | server->on("/0wifi", std::bind(&WiFiManager::handleWifi, this, false)); 129 | server->on("/wifisave", std::bind(&WiFiManager::handleWifiSave, this)); 130 | server->on("/i", std::bind(&WiFiManager::handleInfo, this)); 131 | server->on("/r", std::bind(&WiFiManager::handleReset, this)); 132 | //server->on("/generate_204", std::bind(&WiFiManager::handle204, this)); //Android/Chrome OS captive portal check. 133 | server->on("/fwlink", std::bind(&WiFiManager::handleRoot, this)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. 134 | server->onNotFound (std::bind(&WiFiManager::handleNotFound, this)); 135 | server->begin(); // Web server start 136 | DEBUG_WM(F("HTTP server started")); 137 | 138 | } 139 | 140 | boolean WiFiManager::autoConnect() { 141 | String ssid = "ESP" + String(ESP_getChipId()); 142 | return autoConnect(ssid.c_str(), NULL); 143 | } 144 | 145 | boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) { 146 | DEBUG_WM(F("")); 147 | DEBUG_WM(F("AutoConnect")); 148 | 149 | // read eeprom for ssid and pass 150 | //String ssid = getSSID(); 151 | //String pass = getPassword(); 152 | 153 | // attempt to connect; should it fail, fall back to AP 154 | WiFi.mode(WIFI_STA); 155 | 156 | if (connectWifi("", "") == WL_CONNECTED) { 157 | DEBUG_WM(F("IP Address:")); 158 | DEBUG_WM(WiFi.localIP()); 159 | //connected 160 | return true; 161 | } 162 | 163 | return startConfigPortal(apName, apPassword); 164 | } 165 | 166 | boolean WiFiManager::configPortalHasTimeout() { 167 | #if defined(ESP8266) 168 | if (_configPortalTimeout == 0 || wifi_softap_get_station_num() > 0) { 169 | #else 170 | if (_configPortalTimeout == 0) { // TODO 171 | #endif 172 | _configPortalStart = millis(); // kludge, bump configportal start time to skew timeouts 173 | return false; 174 | } 175 | return (millis() > _configPortalStart + _configPortalTimeout); 176 | } 177 | 178 | boolean WiFiManager::startConfigPortal() { 179 | String ssid = "ESP" + String(ESP_getChipId()); 180 | return startConfigPortal(ssid.c_str(), NULL); 181 | } 182 | 183 | boolean WiFiManager::startConfigPortal(char const *apName, char const *apPassword) { 184 | //setup AP 185 | WiFi.mode(WIFI_AP_STA); 186 | DEBUG_WM("SET AP STA"); 187 | 188 | _apName = apName; 189 | _apPassword = apPassword; 190 | 191 | //notify we entered AP mode 192 | if ( _apcallback != NULL) { 193 | _apcallback(this); 194 | } 195 | 196 | connect = false; 197 | setupConfigPortal(); 198 | 199 | while (1) { 200 | 201 | // check if timeout 202 | if (configPortalHasTimeout()) break; 203 | 204 | //DNS 205 | dnsServer->processNextRequest(); 206 | //HTTP 207 | server->handleClient(); 208 | 209 | 210 | if (connect) { 211 | connect = false; 212 | delay(2000); 213 | DEBUG_WM(F("Connecting to new AP")); 214 | 215 | // using user-provided _ssid, _pass in place of system-stored ssid and pass 216 | if (connectWifi(_ssid, _pass) != WL_CONNECTED) { 217 | DEBUG_WM(F("Failed to connect.")); 218 | } else { 219 | //connected 220 | WiFi.mode(WIFI_STA); 221 | //notify that configuration has changed and any optional parameters should be saved 222 | if ( _savecallback != NULL) { 223 | //todo: check if any custom parameters actually exist, and check if they really changed maybe 224 | _savecallback(); 225 | } 226 | break; 227 | } 228 | 229 | if (_shouldBreakAfterConfig) { 230 | //flag set to exit after config after trying to connect 231 | //notify that configuration has changed and any optional parameters should be saved 232 | if ( _savecallback != NULL) { 233 | //todo: check if any custom parameters actually exist, and check if they really changed maybe 234 | _savecallback(); 235 | } 236 | break; 237 | } 238 | } 239 | yield(); 240 | } 241 | 242 | server.reset(); 243 | dnsServer.reset(); 244 | 245 | return WiFi.status() == WL_CONNECTED; 246 | } 247 | 248 | 249 | int WiFiManager::connectWifi(String ssid, String pass) { 250 | DEBUG_WM(F("Connecting as wifi client...")); 251 | 252 | // check if we've got static_ip settings, if we do, use those. 253 | if (_sta_static_ip) { 254 | DEBUG_WM(F("Custom STA IP/GW/Subnet")); 255 | WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn); 256 | DEBUG_WM(WiFi.localIP()); 257 | } 258 | //fix for auto connect racing issue 259 | if (WiFi.status() == WL_CONNECTED) { 260 | DEBUG_WM("Already connected. Bailing out."); 261 | return WL_CONNECTED; 262 | } 263 | //check if we have ssid and pass and force those, if not, try with last saved values 264 | if (ssid != "") { 265 | WiFi.begin(ssid.c_str(), pass.c_str()); 266 | } else { 267 | if (WiFi.SSID()) { 268 | DEBUG_WM("Using last saved values, should be faster"); 269 | #if defined(ESP8266) 270 | //trying to fix connection in progress hanging 271 | ETS_UART_INTR_DISABLE(); 272 | wifi_station_disconnect(); 273 | ETS_UART_INTR_ENABLE(); 274 | #else 275 | esp_wifi_disconnect(); 276 | #endif 277 | 278 | WiFi.begin(); 279 | } else { 280 | DEBUG_WM("No saved credentials"); 281 | } 282 | } 283 | 284 | int connRes = waitForConnectResult(); 285 | DEBUG_WM ("Connection result: "); 286 | DEBUG_WM ( connRes ); 287 | //not connected, WPS enabled, no pass - first attempt 288 | if (_tryWPS && connRes != WL_CONNECTED && pass == "") { 289 | startWPS(); 290 | //should be connected at the end of WPS 291 | connRes = waitForConnectResult(); 292 | } 293 | return connRes; 294 | } 295 | 296 | uint8_t WiFiManager::waitForConnectResult() { 297 | if (_connectTimeout == 0) { 298 | return WiFi.waitForConnectResult(); 299 | } else { 300 | DEBUG_WM (F("Waiting for connection result with time out")); 301 | unsigned long start = millis(); 302 | boolean keepConnecting = true; 303 | uint8_t status; 304 | while (keepConnecting) { 305 | status = WiFi.status(); 306 | if (millis() > start + _connectTimeout) { 307 | keepConnecting = false; 308 | DEBUG_WM (F("Connection timed out")); 309 | } 310 | if (status == WL_CONNECTED || status == WL_CONNECT_FAILED) { 311 | keepConnecting = false; 312 | } 313 | delay(100); 314 | 315 | } 316 | return status; 317 | } 318 | } 319 | 320 | void WiFiManager::startWPS() { 321 | #if defined(ESP8266) 322 | DEBUG_WM("START WPS"); 323 | WiFi.beginWPSConfig(); 324 | DEBUG_WM("END WPS"); 325 | #else 326 | // TODO 327 | DEBUG_WM("ESP32 WPS TODO"); 328 | #endif 329 | } 330 | 331 | String WiFiManager::getSSID() { 332 | if (_ssid == "") { 333 | DEBUG_WM(F("Reading SSID")); 334 | _ssid = WiFi.SSID(); 335 | DEBUG_WM(F("SSID: ")); 336 | DEBUG_WM(_ssid); 337 | } 338 | return _ssid; 339 | } 340 | 341 | String WiFiManager::getPassword() { 342 | if (_pass == "") { 343 | DEBUG_WM(F("Reading Password")); 344 | _pass = WiFi.psk(); 345 | DEBUG_WM("Password: " + _pass); 346 | //DEBUG_WM(_pass); 347 | } 348 | return _pass; 349 | } 350 | 351 | String WiFiManager::getConfigPortalSSID() { 352 | return _apName; 353 | } 354 | 355 | void WiFiManager::resetSettings() { 356 | DEBUG_WM(F("settings invalidated")); 357 | DEBUG_WM(F("THIS MAY CAUSE AP NOT TO START UP PROPERLY. YOU NEED TO COMMENT IT OUT AFTER ERASING THE DATA.")); 358 | // TODO On ESP32 this does not erase the SSID and password. See 359 | // https://github.com/espressif/arduino-esp32/issues/400 360 | // For now, use "make erase_flash". 361 | WiFi.disconnect(true); 362 | //delay(200); 363 | } 364 | void WiFiManager::setTimeout(unsigned long seconds) { 365 | setConfigPortalTimeout(seconds); 366 | } 367 | 368 | void WiFiManager::setConfigPortalTimeout(unsigned long seconds) { 369 | _configPortalTimeout = seconds * 1000; 370 | } 371 | 372 | void WiFiManager::setConnectTimeout(unsigned long seconds) { 373 | _connectTimeout = seconds * 1000; 374 | } 375 | 376 | void WiFiManager::setDebugOutput(boolean debug) { 377 | _debug = debug; 378 | } 379 | 380 | void WiFiManager::setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { 381 | _ap_static_ip = ip; 382 | _ap_static_gw = gw; 383 | _ap_static_sn = sn; 384 | } 385 | 386 | void WiFiManager::setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { 387 | _sta_static_ip = ip; 388 | _sta_static_gw = gw; 389 | _sta_static_sn = sn; 390 | } 391 | 392 | void WiFiManager::setMinimumSignalQuality(int quality) { 393 | _minimumQuality = quality; 394 | } 395 | 396 | void WiFiManager::setBreakAfterConfig(boolean shouldBreak) { 397 | _shouldBreakAfterConfig = shouldBreak; 398 | } 399 | 400 | /** Handle root or redirect to captive portal */ 401 | void WiFiManager::handleRoot() { 402 | DEBUG_WM(F("Handle root")); 403 | if (captivePortal()) { // If caprive portal redirect instead of displaying the page. 404 | return; 405 | } 406 | 407 | String page = FPSTR(HTTP_HEAD); 408 | page.replace("{v}", "Options"); 409 | page += FPSTR(HTTP_SCRIPT); 410 | page += FPSTR(HTTP_STYLE); 411 | page += _customHeadElement; 412 | page += FPSTR(HTTP_HEAD_END); 413 | page += "

"; 414 | page += _apName; 415 | page += "

"; 416 | page += F("

WiFiManager

"); 417 | page += FPSTR(HTTP_PORTAL_OPTIONS); 418 | page += FPSTR(HTTP_END); 419 | 420 | server->sendHeader("Content-Length", String(page.length())); 421 | server->send(200, "text/html", page); 422 | 423 | } 424 | 425 | /** Wifi config page handler */ 426 | void WiFiManager::handleWifi(boolean scan) { 427 | 428 | String page = FPSTR(HTTP_HEAD); 429 | page.replace("{v}", "Config ESP"); 430 | page += FPSTR(HTTP_SCRIPT); 431 | page += FPSTR(HTTP_STYLE); 432 | page += _customHeadElement; 433 | page += FPSTR(HTTP_HEAD_END); 434 | 435 | if (scan) { 436 | int n = WiFi.scanNetworks(); 437 | DEBUG_WM(F("Scan done")); 438 | if (n == 0) { 439 | DEBUG_WM(F("No networks found")); 440 | page += F("No networks found. Refresh to scan again."); 441 | } else { 442 | 443 | //sort networks 444 | int indices[n]; 445 | for (int i = 0; i < n; i++) { 446 | indices[i] = i; 447 | } 448 | 449 | // RSSI SORT 450 | 451 | // old sort 452 | for (int i = 0; i < n; i++) { 453 | for (int j = i + 1; j < n; j++) { 454 | if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { 455 | std::swap(indices[i], indices[j]); 456 | } 457 | } 458 | } 459 | 460 | /*std::sort(indices, indices + n, [](const int & a, const int & b) -> bool 461 | { 462 | return WiFi.RSSI(a) > WiFi.RSSI(b); 463 | });*/ 464 | 465 | // remove duplicates ( must be RSSI sorted ) 466 | if (_removeDuplicateAPs) { 467 | String cssid; 468 | for (int i = 0; i < n; i++) { 469 | if (indices[i] == -1) continue; 470 | cssid = WiFi.SSID(indices[i]); 471 | for (int j = i + 1; j < n; j++) { 472 | if (cssid == WiFi.SSID(indices[j])) { 473 | DEBUG_WM("DUP AP: " + WiFi.SSID(indices[j])); 474 | indices[j] = -1; // set dup aps to index -1 475 | } 476 | } 477 | } 478 | } 479 | 480 | //display networks in page 481 | for (int i = 0; i < n; i++) { 482 | if (indices[i] == -1) continue; // skip dups 483 | DEBUG_WM(WiFi.SSID(indices[i])); 484 | DEBUG_WM(WiFi.RSSI(indices[i])); 485 | int quality = getRSSIasQuality(WiFi.RSSI(indices[i])); 486 | 487 | if (_minimumQuality == -1 || _minimumQuality < quality) { 488 | String item = FPSTR(HTTP_ITEM); 489 | String rssiQ; 490 | rssiQ += quality; 491 | item.replace("{v}", WiFi.SSID(indices[i])); 492 | item.replace("{r}", rssiQ); 493 | #if defined(ESP8266) 494 | if (WiFi.encryptionType(indices[i]) != ENC_TYPE_NONE) { 495 | #else 496 | if (WiFi.encryptionType(indices[i]) != WIFI_AUTH_OPEN) { 497 | #endif 498 | item.replace("{i}", "l"); 499 | } else { 500 | item.replace("{i}", ""); 501 | } 502 | //DEBUG_WM(item); 503 | page += item; 504 | delay(0); 505 | } else { 506 | DEBUG_WM(F("Skipping due to quality")); 507 | } 508 | 509 | } 510 | page += "
"; 511 | } 512 | } 513 | 514 | page += FPSTR(HTTP_FORM_START); 515 | char parLength[2]; 516 | // add the extra parameters to the form 517 | for (int i = 0; i < _paramsCount; i++) { 518 | if (_params[i] == NULL) { 519 | break; 520 | } 521 | 522 | String pitem = FPSTR(HTTP_FORM_PARAM); 523 | if (_params[i]->getID() != NULL) { 524 | pitem.replace("{i}", _params[i]->getID()); 525 | pitem.replace("{n}", _params[i]->getID()); 526 | pitem.replace("{p}", _params[i]->getPlaceholder()); 527 | snprintf(parLength, 2, "%d", _params[i]->getValueLength()); 528 | pitem.replace("{l}", parLength); 529 | pitem.replace("{v}", _params[i]->getValue()); 530 | pitem.replace("{c}", _params[i]->getCustomHTML()); 531 | } else { 532 | pitem = _params[i]->getCustomHTML(); 533 | } 534 | 535 | page += pitem; 536 | } 537 | if (_params[0] != NULL) { 538 | page += "
"; 539 | } 540 | 541 | if (_sta_static_ip) { 542 | 543 | String item = FPSTR(HTTP_FORM_PARAM); 544 | item.replace("{i}", "ip"); 545 | item.replace("{n}", "ip"); 546 | item.replace("{p}", "Static IP"); 547 | item.replace("{l}", "15"); 548 | item.replace("{v}", _sta_static_ip.toString()); 549 | 550 | page += item; 551 | 552 | item = FPSTR(HTTP_FORM_PARAM); 553 | item.replace("{i}", "gw"); 554 | item.replace("{n}", "gw"); 555 | item.replace("{p}", "Static Gateway"); 556 | item.replace("{l}", "15"); 557 | item.replace("{v}", _sta_static_gw.toString()); 558 | 559 | page += item; 560 | 561 | item = FPSTR(HTTP_FORM_PARAM); 562 | item.replace("{i}", "sn"); 563 | item.replace("{n}", "sn"); 564 | item.replace("{p}", "Subnet"); 565 | item.replace("{l}", "15"); 566 | item.replace("{v}", _sta_static_sn.toString()); 567 | 568 | page += item; 569 | 570 | page += "
"; 571 | } 572 | 573 | page += FPSTR(HTTP_FORM_END); 574 | page += FPSTR(HTTP_SCAN_LINK); 575 | 576 | page += FPSTR(HTTP_END); 577 | 578 | server->sendHeader("Content-Length", String(page.length())); 579 | server->send(200, "text/html", page); 580 | 581 | 582 | DEBUG_WM(F("Sent config page")); 583 | } 584 | 585 | /** Handle the WLAN save form and redirect to WLAN config page again */ 586 | void WiFiManager::handleWifiSave() { 587 | DEBUG_WM(F("WiFi save")); 588 | 589 | //SAVE/connect here 590 | _ssid = server->arg("s").c_str(); 591 | _pass = server->arg("p").c_str(); 592 | 593 | //parameters 594 | for (int i = 0; i < _paramsCount; i++) { 595 | if (_params[i] == NULL) { 596 | break; 597 | } 598 | //read parameter 599 | String value = server->arg(_params[i]->getID()).c_str(); 600 | //store it in array 601 | value.toCharArray(_params[i]->_value, _params[i]->_length); 602 | DEBUG_WM(F("Parameter")); 603 | DEBUG_WM(_params[i]->getID()); 604 | DEBUG_WM(value); 605 | } 606 | 607 | if (server->arg("ip") != "") { 608 | DEBUG_WM(F("static ip")); 609 | DEBUG_WM(server->arg("ip")); 610 | //_sta_static_ip.fromString(server->arg("ip")); 611 | String ip = server->arg("ip"); 612 | optionalIPFromString(&_sta_static_ip, ip.c_str()); 613 | } 614 | if (server->arg("gw") != "") { 615 | DEBUG_WM(F("static gateway")); 616 | DEBUG_WM(server->arg("gw")); 617 | String gw = server->arg("gw"); 618 | optionalIPFromString(&_sta_static_gw, gw.c_str()); 619 | } 620 | if (server->arg("sn") != "") { 621 | DEBUG_WM(F("static netmask")); 622 | DEBUG_WM(server->arg("sn")); 623 | String sn = server->arg("sn"); 624 | optionalIPFromString(&_sta_static_sn, sn.c_str()); 625 | } 626 | 627 | String page = FPSTR(HTTP_HEAD); 628 | page.replace("{v}", "Credentials Saved"); 629 | page += FPSTR(HTTP_SCRIPT); 630 | page += FPSTR(HTTP_STYLE); 631 | page += _customHeadElement; 632 | page += FPSTR(HTTP_HEAD_END); 633 | page += FPSTR(HTTP_SAVED); 634 | page += FPSTR(HTTP_END); 635 | 636 | server->sendHeader("Content-Length", String(page.length())); 637 | server->send(200, "text/html", page); 638 | 639 | DEBUG_WM(F("Sent wifi save page")); 640 | 641 | connect = true; //signal ready to connect/reset 642 | } 643 | 644 | /** Handle the info page */ 645 | void WiFiManager::handleInfo() { 646 | DEBUG_WM(F("Info")); 647 | 648 | String page = FPSTR(HTTP_HEAD); 649 | page.replace("{v}", "Info"); 650 | page += FPSTR(HTTP_SCRIPT); 651 | page += FPSTR(HTTP_STYLE); 652 | page += _customHeadElement; 653 | page += FPSTR(HTTP_HEAD_END); 654 | page += F("
"); 655 | page += F("
Chip ID
"); 656 | page += ESP_getChipId(); 657 | page += F("
"); 658 | page += F("
Flash Chip ID
"); 659 | #if defined(ESP8266) 660 | page += ESP.getFlashChipId(); 661 | #else 662 | // TODO 663 | page += F("TODO"); 664 | #endif 665 | page += F("
"); 666 | page += F("
IDE Flash Size
"); 667 | page += ESP.getFlashChipSize(); 668 | page += F(" bytes
"); 669 | page += F("
Real Flash Size
"); 670 | #if defined(ESP8266) 671 | page += ESP.getFlashChipRealSize(); 672 | #else 673 | // TODO 674 | page += F("TODO"); 675 | #endif 676 | page += F(" bytes
"); 677 | page += F("
Soft AP IP
"); 678 | page += WiFi.softAPIP().toString(); 679 | page += F("
"); 680 | page += F("
Soft AP MAC
"); 681 | page += WiFi.softAPmacAddress(); 682 | page += F("
"); 683 | page += F("
Station MAC
"); 684 | page += WiFi.macAddress(); 685 | page += F("
"); 686 | page += F("
"); 687 | page += FPSTR(HTTP_END); 688 | 689 | server->sendHeader("Content-Length", String(page.length())); 690 | server->send(200, "text/html", page); 691 | 692 | DEBUG_WM(F("Sent info page")); 693 | } 694 | 695 | /** Handle the reset page */ 696 | void WiFiManager::handleReset() { 697 | DEBUG_WM(F("Reset")); 698 | 699 | String page = FPSTR(HTTP_HEAD); 700 | page.replace("{v}", "Info"); 701 | page += FPSTR(HTTP_SCRIPT); 702 | page += FPSTR(HTTP_STYLE); 703 | page += _customHeadElement; 704 | page += FPSTR(HTTP_HEAD_END); 705 | page += F("Module will reset in a few seconds."); 706 | page += FPSTR(HTTP_END); 707 | 708 | server->sendHeader("Content-Length", String(page.length())); 709 | server->send(200, "text/html", page); 710 | 711 | DEBUG_WM(F("Sent reset page")); 712 | delay(5000); 713 | #if defined(ESP8266) 714 | ESP.reset(); 715 | #else 716 | ESP.restart(); 717 | #endif 718 | delay(2000); 719 | } 720 | 721 | void WiFiManager::handleNotFound() { 722 | if (captivePortal()) { // If captive portal redirect instead of displaying the error page. 723 | return; 724 | } 725 | String message = "File Not Found\n\n"; 726 | message += "URI: "; 727 | message += server->uri(); 728 | message += "\nMethod: "; 729 | message += ( server->method() == HTTP_GET ) ? "GET" : "POST"; 730 | message += "\nArguments: "; 731 | message += server->args(); 732 | message += "\n"; 733 | 734 | for ( uint8_t i = 0; i < server->args(); i++ ) { 735 | message += " " + server->argName ( i ) + ": " + server->arg ( i ) + "\n"; 736 | } 737 | server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 738 | server->sendHeader("Pragma", "no-cache"); 739 | server->sendHeader("Expires", "-1"); 740 | server->sendHeader("Content-Length", String(message.length())); 741 | server->send ( 404, "text/plain", message ); 742 | } 743 | 744 | 745 | /** 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. */ 746 | boolean WiFiManager::captivePortal() { 747 | if (!isIp(server->hostHeader()) ) { 748 | DEBUG_WM(F("Request redirected to captive portal")); 749 | server->sendHeader("Location", String("http://") + toStringIp(server->client().localIP()), true); 750 | server->send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. 751 | server->client().stop(); // Stop is needed because we sent no content length 752 | return true; 753 | } 754 | return false; 755 | } 756 | 757 | //start up config portal callback 758 | void WiFiManager::setAPCallback( void (*func)(WiFiManager* myWiFiManager) ) { 759 | _apcallback = func; 760 | } 761 | 762 | //start up save config callback 763 | void WiFiManager::setSaveConfigCallback( void (*func)(void) ) { 764 | _savecallback = func; 765 | } 766 | 767 | //sets a custom element to add to head, like a new style tag 768 | void WiFiManager::setCustomHeadElement(const char* element) { 769 | _customHeadElement = element; 770 | } 771 | 772 | //if this is true, remove duplicated Access Points - defaut true 773 | void WiFiManager::setRemoveDuplicateAPs(boolean removeDuplicates) { 774 | _removeDuplicateAPs = removeDuplicates; 775 | } 776 | 777 | 778 | 779 | template 780 | void WiFiManager::DEBUG_WM(Generic text) { 781 | if (_debug) { 782 | Serial.print("*WM: "); 783 | Serial.println(text); 784 | } 785 | } 786 | 787 | int WiFiManager::getRSSIasQuality(int RSSI) { 788 | int quality = 0; 789 | 790 | if (RSSI <= -100) { 791 | quality = 0; 792 | } else if (RSSI >= -50) { 793 | quality = 100; 794 | } else { 795 | quality = 2 * (RSSI + 100); 796 | } 797 | return quality; 798 | } 799 | 800 | /** Is this an IP? */ 801 | boolean WiFiManager::isIp(String str) { 802 | for (int i = 0; i < str.length(); i++) { 803 | int c = str.charAt(i); 804 | if (c != '.' && (c < '0' || c > '9')) { 805 | return false; 806 | } 807 | } 808 | return true; 809 | } 810 | 811 | /** IP to String? */ 812 | String WiFiManager::toStringIp(IPAddress ip) { 813 | String res = ""; 814 | for (int i = 0; i < 3; i++) { 815 | res += String((ip >> (8 * i)) & 0xFF) + "."; 816 | } 817 | res += String(((ip >> 8 * 3)) & 0xFF); 818 | return res; 819 | } 820 | -------------------------------------------------------------------------------- /WiFiManager.h: -------------------------------------------------------------------------------- 1 | /************************************************************** 2 | WiFiManager is a library for the ESP8266/Arduino platform 3 | (https://github.com/esp8266/Arduino) to enable easy 4 | configuration and reconfiguration of WiFi credentials using a Captive Portal 5 | inspired by: 6 | http://www.esp8266.com/viewtopic.php?f=29&t=2520 7 | https://github.com/chriscook8/esp-arduino-apboot 8 | https://github.com/esp8266/Arduino/tree/master/libraries/DNSServer/examples/CaptivePortalAdvanced 9 | Built by AlexT https://github.com/tzapu 10 | Licensed under MIT license 11 | **************************************************************/ 12 | 13 | #ifndef WiFiManager_h 14 | #define WiFiManager_h 15 | 16 | #if defined(ESP8266) 17 | #include 18 | #include 19 | #else 20 | #include 21 | #include "WebServer.h" 22 | #endif 23 | #include "DNSServer.h" 24 | #include 25 | 26 | #if defined(ESP8266) 27 | extern "C" { 28 | #include "user_interface.h" 29 | } 30 | #define ESP_getChipId() (ESP.getChipId()) 31 | #else 32 | #include 33 | #define ESP_getChipId() ((uint32_t)ESP.getEfuseMac()) 34 | #endif 35 | 36 | const char HTTP_HEAD[] PROGMEM = "{v}"; 37 | const char HTTP_STYLE[] PROGMEM = ""; 38 | const char HTTP_SCRIPT[] PROGMEM = ""; 39 | const char HTTP_HEAD_END[] PROGMEM = "
"; 40 | const char HTTP_PORTAL_OPTIONS[] PROGMEM = "


"; 41 | //

"; 42 | const char HTTP_ITEM[] PROGMEM = "
{v} {r}%
"; 43 | const char HTTP_FORM_START[] PROGMEM = "


"; 44 | const char HTTP_FORM_PARAM[] PROGMEM = "
"; 45 | const char HTTP_FORM_END[] PROGMEM = "
"; 46 | const char HTTP_SCAN_LINK[] PROGMEM = "
"; 47 | const char HTTP_SAVED[] PROGMEM = "
Credentials Saved
Trying to connect Big Circle 24 to the network.
If it fails, reconnect to AP to try again
"; 48 | const char HTTP_END[] PROGMEM = "
"; 49 | 50 | #define WIFI_MANAGER_MAX_PARAMS 10 51 | 52 | class WiFiManagerParameter { 53 | public: 54 | WiFiManagerParameter(const char *custom); 55 | WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length); 56 | WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom); 57 | 58 | const char *getID(); 59 | const char *getValue(); 60 | const char *getPlaceholder(); 61 | int getValueLength(); 62 | const char *getCustomHTML(); 63 | private: 64 | const char *_id; 65 | const char *_placeholder; 66 | char *_value; 67 | int _length; 68 | const char *_customHTML; 69 | 70 | void init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom); 71 | 72 | friend class WiFiManager; 73 | }; 74 | 75 | 76 | class WiFiManager 77 | { 78 | public: 79 | WiFiManager(); 80 | 81 | boolean autoConnect(); 82 | boolean autoConnect(char const *apName, char const *apPassword = NULL); 83 | 84 | //if you want to always start the config portal, without trying to connect first 85 | boolean startConfigPortal(); 86 | boolean startConfigPortal(char const *apName, char const *apPassword = NULL); 87 | 88 | // get the AP name of the config portal, so it can be used in the callback 89 | String getConfigPortalSSID(); 90 | String getSSID(); 91 | String getPassword(); 92 | void resetSettings(); 93 | 94 | //sets timeout before webserver loop ends and exits even if there has been no setup. 95 | //useful for devices that failed to connect at some point and got stuck in a webserver loop 96 | //in seconds setConfigPortalTimeout is a new name for setTimeout 97 | void setConfigPortalTimeout(unsigned long seconds); 98 | void setTimeout(unsigned long seconds); 99 | 100 | //sets timeout for which to attempt connecting, useful if you get a lot of failed connects 101 | void setConnectTimeout(unsigned long seconds); 102 | 103 | 104 | void setDebugOutput(boolean debug); 105 | //defaults to not showing anything under 8% signal quality if called 106 | void setMinimumSignalQuality(int quality = 8); 107 | //sets a custom ip /gateway /subnet configuration 108 | void setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); 109 | //sets config for a static IP 110 | void setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); 111 | //called when AP mode and config portal is started 112 | void setAPCallback( void (*func)(WiFiManager*) ); 113 | //called when settings have been changed and connection was successful 114 | void setSaveConfigCallback( void (*func)(void) ); 115 | //adds a custom parameter 116 | void addParameter(WiFiManagerParameter *p); 117 | //if this is set, it will exit after config, even if connection is unsuccessful. 118 | void setBreakAfterConfig(boolean shouldBreak); 119 | //if this is set, try WPS setup when starting (this will delay config portal for up to 2 mins) 120 | //TODO 121 | //if this is set, customise style 122 | void setCustomHeadElement(const char* element); 123 | //if this is true, remove duplicated Access Points - defaut true 124 | void setRemoveDuplicateAPs(boolean removeDuplicates); 125 | 126 | private: 127 | std::unique_ptr dnsServer; 128 | #ifdef ESP8266 129 | std::unique_ptr server; 130 | #else 131 | std::unique_ptr server; 132 | #endif 133 | 134 | //const int WM_DONE = 0; 135 | //const int WM_WAIT = 10; 136 | 137 | //const String HTTP_HEAD = "{v}"; 138 | 139 | void setupConfigPortal(); 140 | void startWPS(); 141 | 142 | const char* _apName = "no-net"; 143 | const char* _apPassword = NULL; 144 | String _ssid = ""; 145 | String _pass = ""; 146 | unsigned long _configPortalTimeout = 0; 147 | unsigned long _connectTimeout = 0; 148 | unsigned long _configPortalStart = 0; 149 | 150 | IPAddress _ap_static_ip; 151 | IPAddress _ap_static_gw; 152 | IPAddress _ap_static_sn; 153 | IPAddress _sta_static_ip; 154 | IPAddress _sta_static_gw; 155 | IPAddress _sta_static_sn; 156 | 157 | int _paramsCount = 0; 158 | int _minimumQuality = -1; 159 | boolean _removeDuplicateAPs = true; 160 | boolean _shouldBreakAfterConfig = false; 161 | boolean _tryWPS = false; 162 | 163 | const char* _customHeadElement = ""; 164 | 165 | //String getEEPROMString(int start, int len); 166 | //void setEEPROMString(int start, int len, String string); 167 | 168 | int status = WL_IDLE_STATUS; 169 | int connectWifi(String ssid, String pass); 170 | uint8_t waitForConnectResult(); 171 | 172 | void handleRoot(); 173 | void handleWifi(boolean scan); 174 | void handleWifiSave(); 175 | void handleInfo(); 176 | void handleReset(); 177 | void handleNotFound(); 178 | void handle204(); 179 | boolean captivePortal(); 180 | boolean configPortalHasTimeout(); 181 | 182 | // DNS server 183 | const byte DNS_PORT = 53; 184 | 185 | //helpers 186 | int getRSSIasQuality(int RSSI); 187 | boolean isIp(String str); 188 | String toStringIp(IPAddress ip); 189 | 190 | boolean connect; 191 | boolean _debug = true; 192 | 193 | void (*_apcallback)(WiFiManager*) = NULL; 194 | void (*_savecallback)(void) = NULL; 195 | 196 | WiFiManagerParameter* _params[WIFI_MANAGER_MAX_PARAMS]; 197 | 198 | template 199 | void DEBUG_WM(Generic text); 200 | 201 | template 202 | auto optionalIPFromString(T *obj, const char *s) -> decltype( obj->fromString(s) ) { 203 | return obj->fromString(s); 204 | } 205 | auto optionalIPFromString(...) -> bool { 206 | DEBUG_WM("NO fromString METHOD ON IPAddress, you need ESP8266 core 2.1.0 or newer for Custom IP configuration to work."); 207 | return false; 208 | } 209 | }; 210 | 211 | #endif 212 | -------------------------------------------------------------------------------- /detail/RequestHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef REQUESTHANDLER_H 2 | #define REQUESTHANDLER_H 3 | 4 | class RequestHandler { 5 | public: 6 | virtual ~RequestHandler() { } 7 | virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; } 8 | virtual bool canUpload(String uri) { (void) uri; return false; } 9 | virtual bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; } 10 | virtual void upload(WebServer& server, String requestUri, HTTPUpload& upload) { (void) server; (void) requestUri; (void) upload; } 11 | 12 | RequestHandler* next() { return _next; } 13 | void next(RequestHandler* r) { _next = r; } 14 | 15 | private: 16 | RequestHandler* _next = nullptr; 17 | }; 18 | 19 | #endif //REQUESTHANDLER_H 20 | -------------------------------------------------------------------------------- /detail/RequestHandlersImpl.h: -------------------------------------------------------------------------------- 1 | #ifndef REQUESTHANDLERSIMPL_H 2 | #define REQUESTHANDLERSIMPL_H 3 | 4 | #include "RequestHandler.h" 5 | 6 | class FunctionRequestHandler : public RequestHandler { 7 | public: 8 | FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const String &uri, HTTPMethod method) 9 | : _fn(fn) 10 | , _ufn(ufn) 11 | , _uri(uri) 12 | , _method(method) 13 | { 14 | } 15 | 16 | bool canHandle(HTTPMethod requestMethod, String requestUri) override { 17 | if (_method != HTTP_ANY && _method != requestMethod) 18 | return false; 19 | 20 | if (requestUri != _uri) 21 | return false; 22 | 23 | return true; 24 | } 25 | 26 | bool canUpload(String requestUri) override { 27 | if (!_ufn || !canHandle(HTTP_POST, requestUri)) 28 | return false; 29 | 30 | return true; 31 | } 32 | 33 | bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override { 34 | (void) server; 35 | if (!canHandle(requestMethod, requestUri)) 36 | return false; 37 | 38 | _fn(); 39 | return true; 40 | } 41 | 42 | void upload(WebServer& server, String requestUri, HTTPUpload& upload) override { 43 | (void) server; 44 | (void) upload; 45 | if (canUpload(requestUri)) 46 | _ufn(); 47 | } 48 | 49 | protected: 50 | WebServer::THandlerFunction _fn; 51 | WebServer::THandlerFunction _ufn; 52 | String _uri; 53 | HTTPMethod _method; 54 | }; 55 | 56 | class StaticRequestHandler : public RequestHandler { 57 | public: 58 | StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header) 59 | : _fs(fs) 60 | , _uri(uri) 61 | , _path(path) 62 | , _cache_header(cache_header) 63 | { 64 | _isFile = fs.exists(path); 65 | #ifdef ESP8266 66 | DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header); 67 | #else 68 | #ifdef DEBUG_ESP_HTTP_SERVER 69 | DEBUG_OUTPUT.printf("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header); 70 | #endif 71 | #endif 72 | _baseUriLength = _uri.length(); 73 | } 74 | 75 | bool canHandle(HTTPMethod requestMethod, String requestUri) override { 76 | if (requestMethod != HTTP_GET) 77 | return false; 78 | 79 | if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri)) 80 | return false; 81 | 82 | return true; 83 | } 84 | 85 | bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override { 86 | if (!canHandle(requestMethod, requestUri)) 87 | return false; 88 | 89 | #ifdef ESP8266 90 | DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str()); 91 | #else 92 | #ifdef DEBUG_ESP_HTTP_SERVER 93 | DEBUG_OUTPUT.printf("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str()); 94 | #endif 95 | #endif 96 | 97 | String path(_path); 98 | 99 | if (!_isFile) { 100 | // Base URI doesn't point to a file. 101 | // If a directory is requested, look for index file. 102 | if (requestUri.endsWith("/")) requestUri += "index.htm"; 103 | 104 | // Append whatever follows this URI in request to get the file path. 105 | path += requestUri.substring(_baseUriLength); 106 | } 107 | #ifdef ESP8266 108 | DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile); 109 | #else 110 | #ifdef DEBUG_ESP_HTTP_SERVER 111 | DEBUG_OUTPUT.printf("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile); 112 | #endif 113 | #endif 114 | 115 | String contentType = getContentType(path); 116 | 117 | // look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for 118 | // if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc... 119 | if (!path.endsWith(".gz") && !_fs.exists(path)) { 120 | String pathWithGz = path + ".gz"; 121 | if(_fs.exists(pathWithGz)) 122 | path += ".gz"; 123 | } 124 | 125 | File f = _fs.open(path, "r"); 126 | if (!f) 127 | return false; 128 | 129 | if (_cache_header.length() != 0) 130 | server.sendHeader("Cache-Control", _cache_header); 131 | 132 | server.streamFile(f, contentType); 133 | return true; 134 | } 135 | 136 | static String getContentType(const String& path) { 137 | if (path.endsWith(".html")) return "text/html"; 138 | else if (path.endsWith(".htm")) return "text/html"; 139 | else if (path.endsWith(".css")) return "text/css"; 140 | else if (path.endsWith(".txt")) return "text/plain"; 141 | else if (path.endsWith(".js")) return "application/javascript"; 142 | else if (path.endsWith(".json")) return "application/json"; 143 | else if (path.endsWith(".png")) return "image/png"; 144 | else if (path.endsWith(".gif")) return "image/gif"; 145 | else if (path.endsWith(".jpg")) return "image/jpeg"; 146 | else if (path.endsWith(".ico")) return "image/x-icon"; 147 | else if (path.endsWith(".svg")) return "image/svg+xml"; 148 | else if (path.endsWith(".ttf")) return "application/x-font-ttf"; 149 | else if (path.endsWith(".otf")) return "application/x-font-opentype"; 150 | else if (path.endsWith(".woff")) return "application/font-woff"; 151 | else if (path.endsWith(".woff2")) return "application/font-woff2"; 152 | else if (path.endsWith(".eot")) return "application/vnd.ms-fontobject"; 153 | else if (path.endsWith(".sfnt")) return "application/font-sfnt"; 154 | else if (path.endsWith(".xml")) return "text/xml"; 155 | else if (path.endsWith(".pdf")) return "application/pdf"; 156 | else if (path.endsWith(".zip")) return "application/zip"; 157 | else if(path.endsWith(".gz")) return "application/x-gzip"; 158 | else if (path.endsWith(".appcache")) return "text/cache-manifest"; 159 | return "application/octet-stream"; 160 | } 161 | 162 | protected: 163 | FS _fs; 164 | String _uri; 165 | String _path; 166 | String _cache_header; 167 | bool _isFile; 168 | size_t _baseUriLength; 169 | }; 170 | 171 | 172 | #endif //REQUESTHANDLERSIMPL_H 173 | --------------------------------------------------------------------------------