├── LICENSE.txt ├── README.adoc ├── examples └── BasicConfig │ ├── BasicConfig.ino │ └── BasicConfig.ino~ ├── keywords.txt ├── library.properties ├── screenshots └── ESPWebConfig.jpg └── src ├── WebConfig.cpp └── WebConfig.h /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright(C) 2015, Vander 'imerso' Nunes | imersiva.com 2 | 3 | Released under the MIT license. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = WebConfig Class for ESP8266 = 2 | 3 | With this class you can easily have a generic web configurator for 4 | the transceiver, where you can change between Access Point and Station 5 | modes at any time without needing to rewire the module. 6 | 7 | Even when it is configured to work as a Station, if it fails to connect 8 | to the wifi router it will fallback automatically to AP mode, so you 9 | still can access your module from the web. 10 | 11 | ![ESPWebConfig](/screenshots/ESPWebConfig.jpg?raw=true) 12 | 13 | On the very first run, it will start in AP mode on 192.168.0.1 14 | From there, you can use any browser to configure the module. 15 | 16 | This code is to be compiled with Arduino IDE + ESP8266 Board Extension. 17 | 18 | -------------------------------------------------------------------------------- /examples/BasicConfig/BasicConfig.ino: -------------------------------------------------------------------------------- 1 | // =================================== ======= === = == = = = -- - - 2 | // Basic Web Config example 3 | // 4 | // Written by Vander 'imerso' Nunes | imersiva.com 5 | // ======================================= ======= === == == = = = -- - - 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | WebConfig* pWebConfig; 12 | 13 | 14 | // Initialize the system 15 | void setup() 16 | { 17 | pWebConfig = new WebConfig("BASIC WEBCONFIG v1.0", "ESP8266", "8266", false); 18 | } 19 | 20 | 21 | // Serve HTTP configuration interface 22 | void loop() 23 | { 24 | pWebConfig->ProcessHTTP(); 25 | yield(); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /examples/BasicConfig/BasicConfig.ino~: -------------------------------------------------------------------------------- 1 | // =================================== ======= === = == = = = -- - - 2 | // Basic Web Config example 3 | // 4 | // Written by Vander 'imerso' Nunes | imersiva.com 5 | // ======================================= ======= === == == = = = -- - - 6 | 7 | #include 8 | 9 | WebConfig* pWebConfig; 10 | 11 | 12 | // Initialize the system 13 | void setup() 14 | { 15 | pWebConfig = new WebConfig("BASIC WEBCONFIG v1.0", "ESP8266", "8266", false); 16 | } 17 | 18 | 19 | // Serve HTTP configuration interface 20 | void loop() 21 | { 22 | pWebConfig->ProcessHTTP(); 23 | yield(); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For WebConfig 3 | ####################################### 4 | 5 | ####################################### 6 | # Library (KEYWORD1) 7 | ####################################### 8 | 9 | WebConfig KEYWORD1 WebConfig 10 | 11 | ####################################### 12 | # Datatypes (KEYWORD1) 13 | ####################################### 14 | 15 | 16 | ####################################### 17 | # Methods and Functions (KEYWORD2) 18 | ####################################### 19 | 20 | ProcessHTTP KEYWORD2 21 | 22 | ####################################### 23 | # Constants (LITERAL1) 24 | ####################################### 25 | 26 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=WebConfig 2 | version=1.0 3 | author=Vander Nunes 4 | maintainer=Vander Nunes 5 | sentence=ESP8266 Operation Mode Configurator v1.0 6 | paragraph=With this class you can easily have a generic web configurator for the transceiver, where you can change between Access Point and Station modes at any time without needing to rewire the module. 7 | category=Communication 8 | url=http://robotics.imersiva.com/webconfig 9 | architectures=* 10 | -------------------------------------------------------------------------------- /screenshots/ESPWebConfig.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imerso/ESP_WebConfig/4d8305d1b9d583703f7d3ee1b42bc200a011d959/screenshots/ESPWebConfig.jpg -------------------------------------------------------------------------------- /src/WebConfig.cpp: -------------------------------------------------------------------------------- 1 | // =================================== ======= === = == = = = -- - - 2 | // ESP8266 Operation Mode Configurator v1.0 - August 2015 3 | // 4 | // With this class you can easily have a generic web configurator for 5 | // the transceiver, where you can change between Access Point and Station 6 | // modes at any time without needing to rewire the module. 7 | // 8 | // Even when it is configured to work as a Station, if it fails to connect 9 | // to the wifi router it will fallback automatically to AP mode, so you 10 | // still can access your module from the web. 11 | // 12 | // On the very first run, it will start in AP mode on 192.168.0.1 13 | // From there, you can use any browser to configure the module. 14 | // 15 | // This code is to be compiled with Arduino IDE + ESP8266 Board Extension. 16 | // 17 | // Written by Vander 'imerso' Nunes | imersiva.com 18 | // ======================================= ======= === == == = = = -- - - 19 | 20 | #include 21 | 22 | 23 | // Define a software reset function 24 | // to restart when settings are changed 25 | void(*Reset) (void) = 0; 26 | 27 | 28 | // Constructor without initialization 29 | WebConfig::WebConfig() 30 | { 31 | } 32 | 33 | 34 | // Constructor with initialization 35 | // appName : your application name 36 | // defAPName : default AP name, in case of falling back to Access Point mode 37 | // defAPPass : default AP password, in case of falling back to Access Point mode 38 | // doReset : if your application wants to clear EEPROM settings, ask for doReset 39 | WebConfig::WebConfig(const char* appName, const char* defAPName, const char* defAPPass, bool doReset) 40 | { 41 | Init(appName, defAPName, defAPPass, doReset); 42 | } 43 | 44 | 45 | // Initialize the configurator 46 | // appName : your application name 47 | // defAPName : default AP name, in case of falling back to Access Point mode 48 | // defAPPass : default AP password, in case of falling back to Access Point mode 49 | // doReset : if your application wants to clear EEPROM settings, ask for doReset 50 | void WebConfig::Init(const char* appName, const char* defAPName, const char* defAPPass, bool doReset) 51 | { 52 | // update the application name 53 | strncpy(name, appName, 32); 54 | startMillis = millis(); 55 | 56 | // try to load settings from the EEPROM; 57 | // if there are no settings, it fails or user configured to run in AP mode, 58 | // start the AP, else try to connect to the given router ssid/password. 59 | if (doReset || !LoadSettings()) 60 | { 61 | isAP = true; 62 | strncpy(apName, defAPName, 32); 63 | strncpy(apPassword, defAPPass, 32); 64 | apChannel = 10; 65 | webPort = 0; 66 | webLogin[0] = 0; 67 | webPassword[0] = 0; 68 | base64Auth[0] = 0; 69 | ssid[0] = 0; 70 | password[0] = 0; 71 | udpPort = 0; 72 | tcpPort = 0; 73 | if (doReset) SaveSettings(); 74 | } 75 | 76 | if (isAP) 77 | { 78 | // settings do not exist, failed or 79 | // the user configured to run in AP mode anyway. 80 | // start in AP mode 81 | StartAPMode(); 82 | } 83 | else 84 | { 85 | // settings were loaded successfully, 86 | // and the user has setup to run as a router client, 87 | // so try to connect to the given router ssid/password. 88 | byte tries = 11; 89 | WiFi.begin(ssid, password); 90 | while (--tries && WiFi.status() != WL_CONNECTED) 91 | { 92 | delay(1000); 93 | } 94 | 95 | if (WiFi.status() != WL_CONNECTED) 96 | { 97 | // failed to connect to the router, 98 | // so force AP mode start 99 | StartAPMode(); 100 | } 101 | } 102 | 103 | if (udpPort != 0) 104 | { 105 | pUdp = new WiFiUDP(); 106 | pUdp->begin(udpPort); 107 | } else pUdp = NULL; 108 | 109 | if (tcpPort != 0) 110 | { 111 | pTcp = new WiFiServer(tcpPort); 112 | pTcp->begin(); 113 | } else pTcp = NULL; 114 | 115 | // the web interface is always listening 116 | if (webPort <= 0 || webPort > 65535) webPort = 80; 117 | pHttpServer = new WiFiServer(webPort); 118 | pHttpServer->begin(); 119 | } 120 | 121 | 122 | // Destructor 123 | WebConfig::~WebConfig() 124 | { 125 | if (pUdp) delete pUdp; 126 | if (pTcp) delete pTcp; 127 | delete pHttpServer; 128 | } 129 | 130 | 131 | // Process HTTP requests -- just call it inside the loop() function 132 | void WebConfig::ProcessHTTP() 133 | { 134 | // Accept any new web connection 135 | WiFiClient httpClient = pHttpServer->available(); 136 | if (!httpClient) return; 137 | 138 | // Read the entire request 139 | String req = httpClient.readString(); 140 | httpClient.flush(); 141 | 142 | // after some time, do not open the web interface anymore. 143 | //if (millis() - startMillis > 2*60000) return; 144 | 145 | // response header 146 | String s; 147 | 148 | if (strlen(webLogin) > 0 || strlen(webPassword) > 0) 149 | { 150 | int authPos = req.indexOf("Authorization: Basic"); 151 | 152 | if (authPos == -1) 153 | { 154 | // request authentication 155 | s = "HTTP/1.0 401 Authorization Required\r\nWWW-Authenticate: Basic realm=\"" + String(apName) + "\"\r\n\r\n"; 156 | s += "

ACCESS DENIED

"; 157 | httpClient.write(s.c_str(), s.length()); 158 | httpClient.flush(); 159 | return; 160 | } 161 | 162 | // there is authentication info, check it 163 | String authInfo = req.substring(authPos + 21); 164 | int endLinePos = authInfo.indexOf("\r"); 165 | if (endLinePos == -1) { httpClient.print("Malformed request."); httpClient.stop(); return; } 166 | authInfo = authInfo.substring(0, endLinePos); 167 | if (strncmp(base64Auth, authInfo.c_str(), 64)) 168 | { 169 | s = "

ACCESS DENIED

"; 170 | httpClient.write(s.c_str(), s.length()); 171 | httpClient.flush(); 172 | return; 173 | } 174 | } 175 | 176 | byte mac[6]; 177 | WiFi.macAddress(mac); 178 | String m = String(mac[0],HEX) + ":" + String(mac[1],HEX) + ":" + String(mac[2],HEX) + ":" + String(mac[3],HEX) + ":" + String(mac[4],HEX) + ":" + String(mac[5],HEX); 179 | 180 | // 181 | // generate HTTP response 182 | // 183 | 184 | // authentication succeeded, proceed normally 185 | s = "HTTP/1.1 200 OK\r\n"; 186 | s += "Content-Type: text/html\r\n\r\n"; 187 | s += "\r\n\r\n"; 188 | 189 | // If there are parms, update variables and save settings 190 | bool updated = ProcessParms(req); 191 | if (updated) 192 | { 193 | s += "Parameters have been updated and microcontroller will restart.

\r\n"; 194 | } 195 | 196 | // javascript to save configuration 197 | s += "\r\n"; 221 | 222 | // write first part of response 223 | httpClient.write(s.c_str(), s.length()); 224 | 225 | // title and mac address 226 | s = "" + String(name) + "
\r\n"; 227 | s += "MAC: " + m + "
\r\n"; 228 | 229 | // web interface configuration 230 | s += "\r\n"; 231 | s += "\r\n"; 232 | s += "\r\n"; 233 | s += "\r\n"; 234 | s += "\r\n"; 235 | s += "\r\n"; 236 | s += "
WEB INTERFACE
Port
Login
Password
Pass Confirm
\r\n"; 237 | 238 | // ap configuration 239 | s += "\r\n"; 240 | s += "\r\n"; 241 | s += "\r\n"; 242 | s += "\r\n"; 245 | s += "\r\n"; 246 | s += "\r\n"; 247 | s += "\r\n"; 248 | s += "
ACCESS POINT
ModeAccess Point
Channel
SSID
Password
Pass Confirm
\r\n"; 249 | 250 | // station configuration 251 | s += "\r\n"; 252 | s += "\r\n"; 253 | s += "\r\n"; 254 | s += "\r\n"; 255 | s += "\r\n"; 256 | s += "\r\n"; 257 | s += "
STATION
ModeStation
SSID
Password
Pass Confirm
\r\n"; 258 | 259 | // udp/tcp ports configuration 260 | s += "\r\n"; 261 | s += "\r\n"; 262 | s += "\r\n"; 263 | s += "\r\n"; 264 | s += "
UDP|TCP LISTENERS
UDP Port
TCP Port
\r\n"; 265 | 266 | // save button 267 | s += "\r\n"; 268 | 269 | // end of HTTP 270 | s += "\r\n"; 271 | 272 | // write second part of response 273 | httpClient.write(s.c_str(), s.length()); 274 | httpClient.flush(); 275 | 276 | if (updated) 277 | { 278 | // give some time 279 | delay(2000); 280 | 281 | // reset the microcontroller 282 | Reset(); 283 | } 284 | } 285 | 286 | 287 | // Read string from EEPROM 288 | bool WebConfig::ReadString(char* pString, short pos, short len) 289 | { 290 | for (short i=0; i 1) { EEPROM.end(); return false; } 322 | 323 | // set the operation mode (0 == Router Client, 1 = AP) 324 | isAP = (val == 1); 325 | 326 | // has settings saved, so read the other settings 327 | // char apName[32] 328 | // char apPassword[32] 329 | // byte apChannel 330 | // char ssid[32] 331 | // char password[32] 332 | // int udpPort; 333 | // int tcpPort; 334 | // int webPort; 335 | // char webLogin[16]; 336 | // char webPassword[16]; 337 | // char base64Auth[64]; 338 | ReadString(apName, 2, 32); 339 | ReadString(apPassword, 34, 32); 340 | apChannel = EEPROM.read(66); 341 | ReadString(ssid, 67, 32); 342 | ReadString(password, 99, 32); 343 | EEPROM.get(131, udpPort); 344 | EEPROM.get(135, tcpPort); 345 | EEPROM.get(139, webPort); 346 | ReadString(webLogin, 143, 16); 347 | ReadString(webPassword, 159, 16); 348 | ReadString(base64Auth, 175, 64); 349 | 350 | EEPROM.end(); 351 | return true; 352 | } 353 | 354 | 355 | // Save settings to EEPROM 356 | bool WebConfig::SaveSettings() 357 | { 358 | EEPROM.begin(512); 359 | 360 | // first byte is our signature: 0xAA 361 | EEPROM.write(0, 0xAA); 362 | 363 | // second byte is the operation mode (0 == Router Client, 1 == AP) 364 | EEPROM.write(1, (isAP ? 1 : 0)); 365 | 366 | // write other settings 367 | // char apName[32] 368 | // char apPassword[32] 369 | // byte apChannel 370 | // char ssid[32] 371 | // char password[32] 372 | // int udpPort; 373 | // int tcpPort; 374 | // int webPort; 375 | // char webLogin[16]; 376 | // char webPassword[16]; 377 | // char base64Auth[64]; 378 | WriteString(apName, 2, 32); 379 | WriteString(apPassword, 34, 32); 380 | EEPROM.write(66, apChannel); 381 | WriteString(ssid, 67, 32); 382 | WriteString(password, 99, 32); 383 | EEPROM.put(131, udpPort); 384 | EEPROM.put(135, tcpPort); 385 | EEPROM.put(139, webPort); 386 | WriteString(webLogin, 143, 16); 387 | WriteString(webPassword, 159, 16); 388 | WriteString(base64Auth, 175, 64); 389 | 390 | EEPROM.commit(); 391 | return true; 392 | } 393 | 394 | 395 | // Start AP Mode 396 | bool WebConfig::StartAPMode() 397 | { 398 | WiFi.disconnect(); 399 | WiFi.mode(WIFI_AP); 400 | 401 | IPAddress ip(192, 168, 0, 1); 402 | IPAddress mask(255, 255, 255, 0); 403 | WiFi.softAPConfig(ip, ip, mask); 404 | WiFi.softAP(apName, apPassword, apChannel); 405 | isAP = true; 406 | 407 | return true; 408 | } 409 | 410 | 411 | // Extract next substring from current position to next separator or end of string 412 | char* WebConfig::Token(char** req, const char* sep) 413 | { 414 | char* pStart = *req; 415 | char* pPos = strstr(*req, sep); 416 | 417 | if (!pPos) 418 | { 419 | // no more separators, return the entire string 420 | return *req; 421 | } 422 | 423 | // return from the start of the string up to the separator 424 | *pPos = 0; 425 | *req = pPos+1; 426 | return pStart; 427 | } 428 | 429 | 430 | // Parse settings values in case the HTTP request includes them. 431 | // Format is: 432 | // /?(webPort)&(webLogin)&(webPassword)&(base64Auth)&(isAP)&(ap_name)&(ap_password)&(ap_channel)&(ssid)&(password)&(udpport)&(tcpport) 433 | bool WebConfig::ProcessParms(String req) 434 | { 435 | if (req.length() == 0) return false; 436 | if (req.indexOf("/?") == -1) return false; 437 | 438 | char* pReq = new char[req.length()+1]; 439 | if (!pReq) return false; 440 | 441 | strcpy(pReq, req.c_str()); 442 | 443 | char* pPos = strstr(pReq, "/?"); 444 | if (!pPos) 445 | { 446 | delete pReq; 447 | return false; 448 | } 449 | 450 | // position over the first parameter 451 | pPos++; 452 | 453 | // read settings 454 | webPort = atoi(Token(&pPos, "&")); 455 | strncpy(webLogin, Token(&pPos,"&"), 16); 456 | strncpy(webPassword, Token(&pPos,"&"), 16); 457 | strncpy(base64Auth, Token(&pPos,"&"), 64); 458 | isAP = (atoi(Token(&pPos, "&")) == 1); // isAP 459 | strncpy(apName, Token(&pPos, "&"), 32); // apName 460 | strncpy(apPassword, Token(&pPos, "&"), 32); // apPassword 461 | apChannel = (byte)atoi(Token(&pPos,"&")); // apChannel 462 | strncpy(ssid, Token(&pPos,"&"), 32); // router ssid 463 | strncpy(password, Token(&pPos,"&"), 32); // router password 464 | udpPort = atoi(Token(&pPos,"&")); // udp port 465 | tcpPort = atoi(Token(&pPos,"&")); // tcp port 466 | 467 | // save setting on EEPROM 468 | SaveSettings(); 469 | 470 | // release temp memory 471 | delete pReq; 472 | 473 | // indicate that parameters were updated 474 | return true; 475 | } 476 | 477 | -------------------------------------------------------------------------------- /src/WebConfig.h: -------------------------------------------------------------------------------- 1 | // =================================== ======= === = == = = = -- - - 2 | // ESP8266 Operation Mode Configurator v1.0 - August 2015 3 | // 4 | // With this class you can easily have a generic web configurator for 5 | // the transceiver, where you can change between Access Point and Station 6 | // modes at any time without needing to rewire the module. 7 | // 8 | // Even when it is configured to work as a Station, if it fails to connect 9 | // to the wifi router it will fallback automatically to AP mode, so you 10 | // still can access your module from the web. 11 | // 12 | // On the very first run, it will start in AP mode on 192.168.0.1 13 | // From there, you can use any browser to configure the module. 14 | // 15 | // This code is to be compiled with Arduino IDE + ESP8266 Board Extension. 16 | // 17 | // Written by Vander 'imerso' Nunes | imersiva.com 18 | // ======================================= ======= === == == = = = -- - - 19 | 20 | #ifndef _WEBCONFIG_H_ 21 | #define _WEBCONFIG_H_ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | class WebConfig 29 | { 30 | public: 31 | 32 | // Constructor without initialization 33 | WebConfig(); 34 | 35 | // Constructor with initialization 36 | // appName : your application name 37 | // defAPName : default AP name, in case of falling back to Access Point mode 38 | // defAPPass : default AP password, in case of falling back to Access Point mode 39 | // doReset : if your application wants to clear EEPROM settings, ask for doReset 40 | WebConfig(const char* appName, const char* defAPName, const char* defAPPass, bool doReset); 41 | 42 | // Initialize the configurator 43 | // appName : your application name 44 | // defAPName : default AP name, in case of falling back to Access Point mode 45 | // defAPPass : default AP password, in case of falling back to Access Point mode 46 | // doReset : if your application wants to clear EEPROM settings, ask for doReset 47 | void Init(const char* appName, const char* defAPName, const char* defAPPass, bool doReset); 48 | 49 | // Destructor 50 | ~WebConfig(); 51 | 52 | // Process HTTP requests -- just call it inside the loop() function 53 | void ProcessHTTP(); 54 | 55 | // Tell if running in AP mode 56 | bool IsAP() { return isAP; } 57 | 58 | // Return pointers to the listening UDP/TCP servers 59 | WiFiUDP* UDP() { return pUdp; } 60 | WiFiServer* TCP() { return pTcp; } 61 | 62 | private: 63 | 64 | // Application Name 65 | // Shown on the webserver interface. 66 | char name[32]; 67 | char webLogin[16]; 68 | char webPassword[16]; 69 | char base64Auth[64]; 70 | int webPort; 71 | 72 | // When it is in AP mode, it will not connect to any router. 73 | // On the very first run, it will start in AP mode so we can connect and configure it. 74 | bool isAP; 75 | long startMillis; 76 | 77 | // AP info in case it's in AP mode. 78 | char apName[32]; 79 | char apPassword[32]; 80 | byte apChannel; 81 | 82 | // Router info in case it's not in AP mode. 83 | char ssid[32]; 84 | char password[32]; 85 | 86 | // Ports different than zero will be listening for connections and packets 87 | int udpPort; 88 | int tcpPort; 89 | 90 | // Servers (only instatiated when their correspondent ports are not zero) 91 | WiFiUDP* pUdp; 92 | WiFiServer* pTcp; 93 | 94 | // http server for the configuration interface - always available 95 | WiFiServer* pHttpServer; 96 | 97 | // Read string from EEPROM 98 | bool ReadString(char* pString, short pos, short len); 99 | 100 | // Write string to EEPROM 101 | bool WriteString(char* pString, short pos, short len); 102 | 103 | // Load settings from EEPROM 104 | bool LoadSettings(); 105 | 106 | // Save settings to EEPROM 107 | bool SaveSettings(); 108 | 109 | // Start AP Mode 110 | bool StartAPMode(); 111 | 112 | // Extract next substring from current position to next separator or end of string 113 | char* Token(char** req, const char* sep); 114 | 115 | // Parse settings values in case the HTTP request includes them. 116 | // Format is: 117 | // /?(webPort)&(webLogin)&(webPassword)&(base64Auth)&(isAP)&(ap_name)&(ap_password)&(ap_channel)&(ssid)&(password)&(udpport)&(tcpport) 118 | bool ProcessParms(String req); 119 | }; 120 | 121 | #endif 122 | 123 | --------------------------------------------------------------------------------