diff --git a/README.md b/README.md index a329f2b..10da7f6 100644 --- a/README.md +++ b/README.md @@ -43,16 +43,19 @@ First attempt at a library. Lots more changes and fixes to do. Contributions are ![ESP8266 WiFi Captive Portal Homepage](http://i.imgur.com/YPvW9eql.png) ![ESP8266 WiFi Captive Portal Configuration](http://i.imgur.com/oicWJ4gl.png) ## Wishlist -- ~~remove dependency on EEPROM library~~ -- ~~move HTML Strings to PROGMEM~~ -- ~~cleanup and streamline code~~ (although this is ongoing) -- if timeout is set, extend it when a page is fetched in AP mode -- ~~add ability to configure more parameters than ssid/password~~ -- ~~maybe allow setting ip of ESP after reboot~~ -- ~~add to Arduino Library Manager~~ -- ~~add to PlatformIO~~ -- add multiple sets of network credentials -- ~~allow users to customize CSS~~ +- [x] remove dependency on EEPROM library +- [x] move HTML Strings to PROGMEM +- [x] cleanup and streamline code (although this is ongoing) +- [x] if timeout is set, extend it when a page is fetched in AP mode +- [x] add ability to configure more parameters than ssid/password +- [x] maybe allow setting ip of ESP after reboot +- [x] add to Arduino Library Manager +- [x] add to PlatformIO +- [ ] add multiple sets of network credentials +- [x] allow users to customize CSS +- [ ] ESP32 support or instructions +- [ ] rewrite documentation for simplicity, based on scenarios/goals +- [ ] rely on the SDK's built in auto connect more than forcing a connect ## Quick Start diff --git a/WiFiManager.cpp b/WiFiManager.cpp index e5998dc..61321aa 100644 --- a/WiFiManager.cpp +++ b/WiFiManager.cpp @@ -5,20 +5,22 @@ inspired by: http://www.esp8266.com/viewtopic.php?f=29&t=2520 https://github.com/chriscook8/esp-arduino-apboot - https://github.com/esp8266/Arduino/tree/esp8266/hardware/esp8266com/esp8266/libraries/DNSServer/examples/CaptivePortalAdvanced + https://github.com/esp8266/Arduino/tree/master/libraries/DNSServer/examples/CaptivePortalAdvanced Built by AlexT https://github.com/tzapu Licensed under MIT license **************************************************************/ #include "WiFiManager.h" +// PARAMETERS + WiFiManagerParameter::WiFiManagerParameter(const char *custom) { - _id = NULL; - _placeholder = NULL; - _length = 0; - _value = NULL; + _id = NULL; + _placeholder = NULL; + _length = 0; + _value = NULL; _labelPlacement = WFM_LABEL_BEFORE; - _customHTML = custom; + _customHTML = custom; } WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length) { @@ -34,16 +36,16 @@ WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placehold } void WiFiManagerParameter::init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom, int labelPlacement) { - _id = id; - _placeholder = placeholder; - _length = length; + _id = id; + _placeholder = placeholder; + _length = length; _labelPlacement = labelPlacement; - _value = new char[length + 1]; + _value = new char[length + 1]; for (int i = 0; i < length; i++) { _value[i] = 0; } if (defaultValue != NULL) { - strncpy(_value, defaultValue, length); + strncpy(_value, defaultValue, length+1); // length+1 due to null terminated string } _customHTML = custom; @@ -68,17 +70,14 @@ const char* WiFiManagerParameter::getCustomHTML() { return _customHTML; } -WiFiManager::WiFiManager() { -} - void WiFiManager::addParameter(WiFiManagerParameter *p) { if(_paramsCount + 1 > WIFI_MANAGER_MAX_PARAMS) { //Max parameters exceeded! - DEBUG_WM("WIFI_MANAGER_MAX_PARAMS exceeded, increase number (in WiFiManager.h) before adding more parameters!"); - DEBUG_WM("Skipping parameter with ID:"); - DEBUG_WM(p->getID()); - return; + DEBUG_WM("WIFI_MANAGER_MAX_PARAMS exceeded, increase number (in WiFiManager.h) before adding more parameters!"); + DEBUG_WM("Skipping parameter with ID:"); + DEBUG_WM(p->getID()); + return; } _params[_paramsCount] = p; _paramsCount++; @@ -86,92 +85,147 @@ void WiFiManager::addParameter(WiFiManagerParameter *p) { DEBUG_WM(p->getID()); } -void WiFiManager::setupConfigPortal() { - dnsServer.reset(new DNSServer()); - server.reset(new ESP8266WebServer(80)); +// constructors +WiFiManager::WiFiManager(Stream& consolePort):_debugPort(consolePort) { + WiFiManager(); +} +WiFiManager::WiFiManager():_debugPort(Serial) { + _usermode = WiFi.getMode(); + WiFi.persistent(false); // disable persistent so scannetworks and mode switching do not cause overwrites +} + +// destructor +WiFiManager::~WiFiManager() { + if(_userpersistent) WiFi.persistent(true); // reenable persistent, there is no getter we rely on _userpersistent + WiFi.mode(_usermode); + DEBUG_WM(F("unloading")); +} + +// AUTOCONNECT +boolean WiFiManager::autoConnect() { + String ssid = "ESP" + String(ESP.getChipId()); + return autoConnect(ssid.c_str(), NULL); +} + +boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) { DEBUG_WM(F("")); - _configPortalStart = millis(); + DEBUG_WM(F("AutoConnect")); - DEBUG_WM(F("Configuring access point... ")); - DEBUG_WM(_apName); - if (_apPassword != NULL) { - if (strlen(_apPassword) < 8 || strlen(_apPassword) > 63) { - // fail passphrase to short or long! - DEBUG_WM(F("Invalid AccessPoint password. Ignoring")); - _apPassword = NULL; - } - DEBUG_WM(_apPassword); + // attempt to connect using saved settings, on fail fallback to AP config portal + WiFi_enableSTA(true); + + // if already connected, or try stored connect + if (WiFi.status() == WL_CONNECTED || connectWifi("", "") == WL_CONNECTED) { + //connected + DEBUG_WM(F("IP Address:")); + DEBUG_WM(WiFi.localIP()); + return true; } + // not connected start configportal + return startConfigPortal(apName, apPassword); +} - //optional soft ip config +// CONFIG PORTAL +bool WiFiManager::startAP(){ + DEBUG_WM(F("Configuring access point with with SSID of ... ")); + DEBUG_WM(_apName); + + // setup optional soft AP static ip config if (_ap_static_ip) { DEBUG_WM(F("Custom AP IP/GW/Subnet")); WiFi.softAPConfig(_ap_static_ip, _ap_static_gw, _ap_static_sn); } - if (_apPassword != NULL) { - WiFi.softAP(_apName, _apPassword);//password option + bool ret = true; + + // start soft AP with password or anonymous + if (_apPassword != "") { + Serial.println((String)_apPassword.c_str()); + ret = WiFi.softAP(_apName.c_str(), _apPassword.c_str());//password option } else { - WiFi.softAP(_apName); + DEBUG_WM(F("AP has anonymous access")); + ret = WiFi.softAP(_apName.c_str()); } - delay(500); // Without delay I've seen the IP address blank + debugSoftAPConfig(); + + if(!ret) DEBUG_WM("There was an error starting the AP"); // @bug startAP returns unreliable success status + + delay(500); // slight delay to make sure we get an AP IP DEBUG_WM(F("AP IP address: ")); DEBUG_WM(WiFi.softAPIP()); + // do AP callback if set + if ( _apcallback != NULL) { + _apcallback(this); + } + + return ret; +} + +void WiFiManager::startWebPortal() { + if(configPortalActive || webPortalActive) return; + setupConfigPortal(); + webPortalActive = true; +} + +void WiFiManager::stopWebPortal() { + if(!configPortalActive && !webPortalActive) return; + DEBUG_WM(F("Stopping Web Portal")); + webPortalActive = false; + stopConfigPortal(); +} + +boolean WiFiManager::configPortalHasTimeout(){ + if(_configPortalTimeout == 0 || wifi_softap_get_station_num() > 0){ + if(millis() - timer > 30000){ + timer = millis(); + DEBUG_WM("NUM CLIENTS: " + (String)wifi_softap_get_station_num()); + } + _configPortalStart = millis(); // kludge, bump configportal start time to skew timeouts + return false; + } + + if(millis() > _configPortalStart + _configPortalTimeout){ + DEBUG_WM(F("config portal has timed out")); + return true; + } else { + if((millis() - timer) > 1000){ + timer = millis(); + DEBUG_WM("Portal Timeout In " + (String)((_configPortalStart + _configPortalTimeout-millis())/1000) + " seconds"); + } + } + + return false; +} + +void WiFiManager::setupConfigPortal() { + + DEBUG_WM("Starting Web Portal"); + + // setup dns and web servers + dnsServer.reset(new DNSServer()); + server.reset(new ESP8266WebServer(80)); + /* Setup the DNS server redirecting all the domains to the apIP */ dnsServer->setErrorReplyCode(DNSReplyCode::NoError); dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); - /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */ + /* Setup httpd callbacks, web pages: root, wifi config pages, SO captive portal detectors and not found. */ server->on("/", std::bind(&WiFiManager::handleRoot, this)); server->on("/wifi", std::bind(&WiFiManager::handleWifi, this, true)); server->on("/0wifi", std::bind(&WiFiManager::handleWifi, this, false)); server->on("/wifisave", std::bind(&WiFiManager::handleWifiSave, this)); server->on("/i", std::bind(&WiFiManager::handleInfo, this)); server->on("/r", std::bind(&WiFiManager::handleReset, this)); + server->on("/exit", std::bind(&WiFiManager::handleExit, this)); + server->on("/erase", std::bind(&WiFiManager::handleErase, this)); //server->on("/fwlink", std::bind(&WiFiManager::handleRoot, this)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. server->onNotFound (std::bind(&WiFiManager::handleNotFound, this)); + server->begin(); // Web server start DEBUG_WM(F("HTTP server started")); - -} - -boolean WiFiManager::autoConnect(boolean showStaticIPFields) { - String ssid = "ESP" + String(ESP.getChipId()); - return autoConnect(ssid.c_str(), NULL, showStaticIPFields); -} - -boolean WiFiManager::autoConnect(char const *apName, char const *apPassword, boolean showStaticIPFields) { - DEBUG_WM(F("")); - DEBUG_WM(F("AutoConnect")); - - _sta_show_static_fields = showStaticIPFields; - - // read eeprom for ssid and pass - //String ssid = getSSID(); - //String pass = getPassword(); - - // attempt to connect; should it fail, fall back to AP - WiFi.mode(WIFI_STA); - - if (connectWifi("", "") == WL_CONNECTED) { - DEBUG_WM(F("IP Address:")); - DEBUG_WM(WiFi.localIP()); - //connected - return true; - } - - return startConfigPortal(apName, apPassword); -} - -boolean WiFiManager::configPortalHasTimeout(){ - if(_configPortalTimeout == 0 || wifi_softap_get_station_num() > 0){ - _configPortalStart = millis(); // kludge, bump configportal start time to skew timeouts - return false; - } - return (millis() > _configPortalStart + _configPortalTimeout); } boolean WiFiManager::startConfigPortal() { @@ -181,134 +235,232 @@ boolean WiFiManager::startConfigPortal() { boolean WiFiManager::startConfigPortal(char const *apName, char const *apPassword) { //setup AP - WiFi.mode(WIFI_AP_STA); - DEBUG_WM("SET AP STA"); + + bool disableSTA = false; // debug, always disable sta - _apName = apName; - _apPassword = apPassword; - - //notify we entered AP mode - if ( _apcallback != NULL) { - _apcallback(this); + // HANDLE issues with STA connections, shutdown sta if not connected, or else this will hang channel scanning and softap will not respond + // @todo sometimes still cannot connect to AP for no known reason, no events in log either + if(!WiFi.isConnected() || disableSTA){ + // this fixes most ap problems, however, simply doing mode(WIFI_AP) does not work if sta connection is hanging, must `wifi_station_disconnect` + WiFi_Disconnect(); + WiFi_enableSTA(false); + DEBUG_WM("Disabling STA"); + } + else { + // @todo even if sta is connected, it is possible that softap connections will fail, IOS says "invalid password", windows says "cannot connect to this network" researching + WiFi_enableSTA(true); } - connect = false; + DEBUG_WM("Enabling AP"); + + _apName = apName; // @todo check valid apname ? + _apPassword = apPassword; + if(!validApPassword()) return false; + + // init configportal globals to known states + configPortalActive = true; + bool result = connect = abort = false; // loop flags, connect true success, abort true break + uint8_t state; + + DEBUG_WM(F("setupConfigPortal")); + _configPortalStart = millis(); + + // start access point + startAP(); + + // init configportal setupConfigPortal(); + if(!_configPortalIsBlocking){ + DEBUG_WM(F("Config Portal Running, non blocking/processing")); + return result; + } + + DEBUG_WM(F("Config Portal Running, blocking, waiting for clients...")); + // blocking loop waiting for config while(1){ - // check if timeout - if(configPortalHasTimeout()) break; + // if timed out or abort, break + if(configPortalHasTimeout() || abort){ + DEBUG_WM(F("configportal abort")); + stopConfigPortal(); + result = abort ? portalAbortResult : portalTimeoutResult; // false, false + break; + } - //DNS + state = handleConfigPortal(); + + // status change, break + if(state != WL_IDLE_STATUS){ + result = state == WL_CONNECTED; // true if connected + break; + } + + yield(); // watchdog + } + + DEBUG_WM("config portal exiting"); + return result; +} + +boolean WiFiManager::process(){ + if(webPortalActive || (configPortalActive && !_configPortalIsBlocking)){ + uint8_t state = handleConfigPortal(); + return state == WL_CONNECTED; + } + return false; +} + +//using esp enums returns for now, should be fine +uint8_t WiFiManager::handleConfigPortal(){ + //DNS handler dnsServer->processNextRequest(); - //HTTP + //HTTP handler server->handleClient(); - + // Waiting for save... if (connect) { connect = false; delay(2000); - DEBUG_WM(F("Connecting to new AP")); + DEBUG_WM(F("Connecting save WiFi")); - // using user-provided _ssid, _pass in place of system-stored ssid and pass - if (connectWifi(_ssid, _pass) != WL_CONNECTED) { - DEBUG_WM(F("Failed to connect.")); - } else { - //connected - WiFi.mode(WIFI_STA); - //notify that configuration has changed and any optional parameters should be saved - if ( _savecallback != NULL) { - //todo: check if any custom parameters actually exist, and check if they really changed maybe - _savecallback(); - } - break; + // attempt sta connection to submitted _ssid, _pass + if (connectWifi(_ssid, _pass) == WL_CONNECTED) { + DEBUG_WM(F("Connect to new AP SUCCESS")); + stopConfigPortal(); + return WL_CONNECTED; // success } + DEBUG_WM(F("Failed to connect.")); + if (_shouldBreakAfterConfig) { - //flag set to exit after config after trying to connect - //notify that configuration has changed and any optional parameters should be saved - if ( _savecallback != NULL) { - //todo: check if any custom parameters actually exist, and check if they really changed maybe - _savecallback(); - } - break; + // this is more of an exiting callback than a save + stopConfigPortal(); + return WL_CONNECT_FAILED; // fail + } + else{ + // sta off to stabilize AP on connect failure + WiFi_Disconnect(); + WiFi_enableSTA(false); + DEBUG_WM("Disabling STA"); } } - yield(); + + return WL_IDLE_STATUS; +} + +boolean WiFiManager::stopConfigPortal(){ + if(webPortalActive) return false; + + // do save callback, @todo move out of here + if ( _savecallback != NULL) { + //todo: confirm or verify data was saved + _savecallback(); } + //DNS handler + dnsServer->processNextRequest(); + //HTTP handler + server->handleClient(); + + // @todo what is the proper way to shutdown and free the server up + server->stop(); server.reset(); dnsServer.reset(); - return WiFi.status() == WL_CONNECTED; + if(!configPortalActive) return false; + + // turn off AP + // @todo bug *WM: disconnect configportal + // [APdisconnect] set_config failed! + // *WM: disconnect configportal - softAPdisconnect failed + DEBUG_WM(F("disconnect configportal")); + bool ret = WiFi.softAPdisconnect(false); + if(!ret)DEBUG_WM(F("disconnect configportal - softAPdisconnect failed")); + WiFi_Mode(_usermode); // restore users wifi mode + configPortalActive = false; + return ret; } - +// @todo refactor this up into seperate functions +// one for connecting to flash , one for new client +// clean up, flow is convoluted, and causes bugs int WiFiManager::connectWifi(String ssid, String pass) { DEBUG_WM(F("Connecting as wifi client...")); + + bool waitforconx = true; - // check if we've got static_ip settings, if we do, use those. + // Setup static IP config if provided if (_sta_static_ip) { DEBUG_WM(F("Custom STA IP/GW/Subnet")); WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn); DEBUG_WM(WiFi.localIP()); } - //fix for auto connect racing issue - if (WiFi.status() == WL_CONNECTED) { - DEBUG_WM("Already connected. Bailing out."); - return WL_CONNECTED; - } - //check if we have ssid and pass and force those, if not, try with last saved values - if (ssid != "") { - WiFi.begin(ssid.c_str(), pass.c_str()); - } else { - if (WiFi.SSID()) { - DEBUG_WM("Using last saved values, should be faster"); - //trying to fix connection in progress hanging - ETS_UART_INTR_DISABLE(); - wifi_station_disconnect(); - ETS_UART_INTR_ENABLE(); + // make sure sta is on before `begin` so it does not call enablesta->mode while persistent is ON ( which would save WM AP state to eeprom !) + WiFi_Disconnect(); // disconnect before begin, in case anything is hung + + // if ssid argument provided connect to that + if (ssid != "") { + DEBUG_WM("Connecting to new AP"); + WiFi_enableSTA(true,storeSTAmode); // storeSTAmode will also toggle STA on in default opmode (persistent) if true (default) + WiFi.persistent(true); + WiFi.begin(ssid.c_str(), pass.c_str()); + WiFi.persistent(false); + //@todo catch failures in set_config + } else { + // connect using saved ssid if there is one + if (WiFi.SSID() != "") { + DEBUG_WM("Connecting to saved AP"); + WiFi_enableSTA(true,storeSTAmode); WiFi.begin(); } else { - DEBUG_WM("No saved credentials"); + DEBUG_WM("No saved credentials, skipping wifi"); + waitforconx = false; } } - int connRes = waitForConnectResult(); + uint8_t connRes = waitforconx ? waitForConnectResult() : WL_NO_SSID_AVAIL; DEBUG_WM ("Connection result: "); - DEBUG_WM ( connRes ); - //not connected, WPS enabled, no pass - first attempt + DEBUG_WM ( getWLStatusString(connRes) ); + + // do WPS, if WPS options enabled and not connected and no password was supplied + // @todo this seems like wrong place for this, is it a fallback or option? if (_tryWPS && connRes != WL_CONNECTED && pass == "") { startWPS(); - //should be connected at the end of WPS + // should be connected at the end of WPS connRes = waitForConnectResult(); } + return connRes; } +// @todo uses _connectTimeout for wifi save also, add timeout argument to bypass? + +uint8_t WiFiManager::waitForConnectResult(uint16_t timeout) { + return waitForConnectResult(timeout * 1000); +} + uint8_t WiFiManager::waitForConnectResult() { - if (_connectTimeout == 0) { + if (_connectTimeout == 0){ + DEBUG_WM (F("connectTimeout not set, ESP waitForConnectResult...")); return WiFi.waitForConnectResult(); - } else { - DEBUG_WM (F("Waiting for connection result with time out")); - unsigned long start = millis(); - boolean keepConnecting = true; - uint8_t status; - while (keepConnecting) { - status = WiFi.status(); - if (millis() > start + _connectTimeout) { - keepConnecting = false; - DEBUG_WM (F("Connection timed out")); - } - if (status == WL_CONNECTED || status == WL_CONNECT_FAILED) { - keepConnecting = false; - } - delay(100); - } - return status; } + + DEBUG_WM (F("connectTimeout set, waiting for connect....")); + uint8_t status; + int timeout = millis() + _connectTimeout; + + while(millis() < timeout) { + status = WiFi.status(); + // @todo detect additional states, connect happens, then dhcp then get ip, there is some delay here, make sure not to timeout if waiting on IP + if (status == WL_CONNECTED || status == WL_CONNECT_FAILED) { + return status; + } + DEBUG_WM ("."); + delay(100); + } + return status; } void WiFiManager::startWPS() { @@ -316,37 +468,22 @@ void WiFiManager::startWPS() { WiFi.beginWPSConfig(); DEBUG_WM("END WPS"); } -/* - String WiFiManager::getSSID() { - if (_ssid == "") { - DEBUG_WM(F("Reading SSID")); - _ssid = WiFi.SSID(); - DEBUG_WM(F("SSID: ")); - DEBUG_WM(_ssid); - } - return _ssid; - } - String WiFiManager::getPassword() { - if (_pass == "") { - DEBUG_WM(F("Reading Password")); - _pass = WiFi.psk(); - DEBUG_WM("Password: " + _pass); - //DEBUG_WM(_pass); - } - return _pass; - } -*/ +// GETTERS String WiFiManager::getConfigPortalSSID() { return _apName; } +// SETTERS void WiFiManager::resetSettings() { - DEBUG_WM(F("settings invalidated")); - DEBUG_WM(F("THIS MAY CAUSE AP NOT TO START UP PROPERLY. YOU NEED TO COMMENT IT OUT AFTER ERASING THE DATA.")); + DEBUG_WM(F("SETTINGS ERASED")); + DEBUG_WM(F("THIS MAY CAUSE AP NOT TO START UP PROPERLY. YOU NEED TO COMMENT IT OUT AFTER ERASING THE DATA.")); // @todo WHUT? + WiFi.persistent(true); WiFi.disconnect(true); + WiFi.persistent(false); //delay(200); } + void WiFiManager::setTimeout(unsigned long seconds) { setConfigPortalTimeout(seconds); } @@ -383,10 +520,13 @@ void WiFiManager::setBreakAfterConfig(boolean shouldBreak) { _shouldBreakAfterConfig = shouldBreak; } -/** Handle root or redirect to captive portal */ +/** + * HTTPD CALLBACK root or redirect to captive portal + */ void WiFiManager::handleRoot() { - DEBUG_WM(F("Handle root")); - if (captivePortal()) { // If caprive portal redirect instead of displaying the page. + DEBUG_WM(F("<- HTTP Root")); + + if (captivePortal()) { // If captive portal redirect instead of displaying the page. return; } @@ -397,19 +537,27 @@ void WiFiManager::handleRoot() { page += _customHeadElement; page += FPSTR(HTTP_HEAD_END); page += "

"; - page += _apName; + page += configPortalActive ? _apName : WiFi.localIP().toString(); page += "

"; page += F("

WiFiManager

"); page += FPSTR(HTTP_PORTAL_OPTIONS); + reportStatus(page); page += FPSTR(HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); server->send(200, "text/html", page); + // server->close(); // testing reliability fix for content length mismatches during mutiple flood hits } -/** Wifi config page handler */ +/** + * HTTPD CALLBACK Wifi config page handler + */ void WiFiManager::handleWifi(boolean scan) { + DEBUG_WM("<- HTTP Wifi"); + String page = FPSTR(HTTP_HEAD); page.replace("{v}", "Config ESP"); page += FPSTR(HTTP_SCRIPT); @@ -418,6 +566,33 @@ void WiFiManager::handleWifi(boolean scan) { page += FPSTR(HTTP_HEAD_END); if (scan) { + page += getScanItemOut(); + } + + String pitem = FPSTR(HTTP_FORM_START); + pitem.replace("{v}", WiFi.SSID()); + page += pitem; + + page += getStaticOut(); + page += getParamOut(); + + page += FPSTR(HTTP_FORM_END); + page += FPSTR(HTTP_SCAN_LINK); + reportStatus(page); + page += FPSTR(HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + // server->close(); // testing reliability fix for content length mismatches during mutiple flood hits + + // Serial.println(page); + + DEBUG_WM(F("Sent config page")); +} + +String WiFiManager::getScanItemOut(){ + String page; + int n = WiFi.scanNetworks(); DEBUG_WM(F("Scan done")); if (n == 0) { @@ -462,124 +637,176 @@ void WiFiManager::handleWifi(boolean scan) { } } + // token precheck, to speed up replacements on large ap lists + String HTTP_ITEM_STR = FPSTR(HTTP_ITEM); + bool tok_r = HTTP_ITEM_STR.indexOf("{r}") > 0; + bool tok_R = HTTP_ITEM_STR.indexOf("{R}") > 0; + bool tok_e = HTTP_ITEM_STR.indexOf("{e}") > 0; + bool tok_q = HTTP_ITEM_STR.indexOf("{q}") > 0; + bool tok_i = HTTP_ITEM_STR.indexOf("{i}") > 0; + //display networks in page for (int i = 0; i < n; i++) { if (indices[i] == -1) continue; // skip dups - DEBUG_WM(WiFi.SSID(indices[i])); - DEBUG_WM(WiFi.RSSI(indices[i])); - int quality = getRSSIasQuality(WiFi.RSSI(indices[i])); - if (_minimumQuality == -1 || _minimumQuality < quality) { + DEBUG_WM("AP: " + (String)WiFi.RSSI(indices[i]) + " " + (String)WiFi.SSID(indices[i])); + + int rssiperc = getRSSIasQuality(WiFi.RSSI(indices[i])); + uint8_t enc_type = WiFi.encryptionType(indices[i]); + + if (_minimumQuality == -1 || _minimumQuality < rssiperc) { String item = FPSTR(HTTP_ITEM); - String rssiQ; - rssiQ += quality; - item.replace("{v}", WiFi.SSID(indices[i])); - item.replace("{r}", rssiQ); - if (WiFi.encryptionType(indices[i]) != ENC_TYPE_NONE) { - item.replace("{i}", "l"); - } else { - item.replace("{i}", ""); + item.replace("{v}", WiFi.SSID(indices[i])); // ssid no encoding + if(tok_e) item.replace("{e}", encryptionTypeStr(enc_type)); + if(tok_r) item.replace("{r}", (String)rssiperc); // rssi percentage 0-100 + if(tok_R) item.replace("{R}", (String)WiFi.RSSI(indices[i])); // rssi db + if(tok_q) item.replace("{q}", (String)round(map(rssiperc,0,100,1,4))); //quality icon 1-4 + if(tok_i){ + if (enc_type != ENC_TYPE_NONE) { + item.replace("{i}", "l"); + } else { + item.replace("{i}", ""); + } } //DEBUG_WM(item); page += item; delay(0); } else { - DEBUG_WM(F("Skipping due to quality")); + DEBUG_WM(F("Skipping , does not meet _minimumQuality")); } } page += "
"; } - } - page += FPSTR(HTTP_FORM_START); - char parLength[2]; - // add the extra parameters to the form - for (int i = 0; i < _paramsCount; i++) { - if (_params[i] == NULL) { - break; - } - - //String pitem = FPSTR(HTTP_FORM_PARAM); - String pitem; - switch (_params[i]->getLabelPlacement()) { - case WFM_LABEL_BEFORE: - pitem = FPSTR(HTTP_FORM_LABEL); - pitem += FPSTR(HTTP_FORM_PARAM); - break; - case WFM_LABEL_AFTER: - pitem = FPSTR(HTTP_FORM_PARAM); - pitem += FPSTR(HTTP_FORM_LABEL); - break; - default: - // WFM_NO_LABEL - pitem = FPSTR(HTTP_FORM_PARAM); - break; - } - if (_params[i]->getID() != NULL) { - pitem.replace("{i}", _params[i]->getID()); - pitem.replace("{n}", _params[i]->getID()); - pitem.replace("{p}", _params[i]->getPlaceholder()); - snprintf(parLength, 2, "%d", _params[i]->getValueLength()); - pitem.replace("{l}", parLength); - pitem.replace("{v}", _params[i]->getValue()); - pitem.replace("{c}", _params[i]->getCustomHTML()); - } else { - pitem = _params[i]->getCustomHTML(); - } - - page += pitem; - } - if (_params[0] != NULL) { - page += "
"; - } - - if (_sta_show_static_fields || _sta_static_ip) { - - String item = FPSTR(HTTP_FORM_PARAM); - item.replace("{i}", "ip"); - item.replace("{n}", "ip"); - item.replace("{p}", "Static IP"); - item.replace("{l}", "15"); - item.replace("{v}", (_sta_static_ip ? _sta_static_ip.toString() : "")); - - page += item; - - item = FPSTR(HTTP_FORM_PARAM); - item.replace("{i}", "gw"); - item.replace("{n}", "gw"); - item.replace("{p}", "Static Gateway"); - item.replace("{l}", "15"); - item.replace("{v}", (_sta_static_gw ? _sta_static_gw.toString() : "")); - - page += item; - - item = FPSTR(HTTP_FORM_PARAM); - item.replace("{i}", "sn"); - item.replace("{n}", "sn"); - item.replace("{p}", "Subnet"); - item.replace("{l}", "15"); - item.replace("{v}", (_sta_static_sn ? _sta_static_sn.toString() : "")); - - page += item; - - page += "
"; - } - - page += FPSTR(HTTP_FORM_END); - page += FPSTR(HTTP_SCAN_LINK); - - page += FPSTR(HTTP_END); - - server->send(200, "text/html", page); - - - DEBUG_WM(F("Sent config page")); + return page; } -/** Handle the WLAN save form and redirect to WLAN config page again */ +String WiFiManager::getStaticOut(){ + String page; + if (_sta_show_static_fields || _sta_static_ip) { + + // @todo how do we get these settings from memory , wifi_get_ip_info does not seem to reveal if struct ip_info is static or not + // @todo move titles to params for i18n + + String item = FPSTR(HTTP_FORM_LABEL); + item += FPSTR(HTTP_FORM_PARAM); + item.replace("{i}", "ip"); + item.replace("{n}", "ip"); + item.replace("{p}", "{t}"); + item.replace("{t}", "Static IP"); + item.replace("{l}", "15"); + item.replace("{v}", (_sta_static_ip ? _sta_static_ip.toString() : "")); + item.replace("{c}", ""); + + // IPAddress sta_ip = WiFi.localIP(); + // item.replace("{p}", sta_ip.toString()); + + page += item; + + item = FPSTR(HTTP_FORM_LABEL); + item += FPSTR(HTTP_FORM_PARAM); + item.replace("{i}", "gw"); + item.replace("{n}", "gw"); + item.replace("{p}", "{t}"); + item.replace("{t}", "Static Gateway"); + item.replace("{l}", "15"); + item.replace("{v}", (_sta_static_gw ? _sta_static_gw.toString() : "")); + item.replace("{c}", ""); + + // IPAddress sta_gateway = WiFi.gatewayIP(); + // item.replace("{p}", sta_gateway.toString()); + + page += item; + + item = FPSTR(HTTP_FORM_LABEL); + item += FPSTR(HTTP_FORM_PARAM); + item.replace("{i}", "sn"); + item.replace("{n}", "sn"); + item.replace("{p}", "{t}"); + item.replace("{t}", "Subnet"); + item.replace("{l}", "15"); + item.replace("{v}", (_sta_static_sn ? _sta_static_sn.toString() : "")); + item.replace("{c}", ""); + + // IPAddress sta_subnet = WiFi.subnetMask(); + // item.replace("{p}", sta_subnet.toString()); + page += item; + page += "
"; // @todo remove these, use css + } + return page; +} + +String WiFiManager::getParamOut(){ + String page; + + if(_paramsCount > 0){ + + String HTTP_PARAM_temp = FPSTR(HTTP_FORM_LABEL); + HTTP_PARAM_temp += FPSTR(HTTP_FORM_PARAM); + bool tok_i = HTTP_PARAM_temp.indexOf("{i}") > 0; + bool tok_n = HTTP_PARAM_temp.indexOf("{n}") > 0; + bool tok_p = HTTP_PARAM_temp.indexOf("{p}") > 0; + bool tok_t = HTTP_PARAM_temp.indexOf("{t}") > 0; + bool tok_l = HTTP_PARAM_temp.indexOf("{l}") > 0; + bool tok_v = HTTP_PARAM_temp.indexOf("{v}") > 0; + bool tok_c = HTTP_PARAM_temp.indexOf("{c}") > 0; + + page += FPSTR(HTTP_FORM_PARAM_START); + + char parLength[5]; + // add the extra parameters to the form + for (int i = 0; i < _paramsCount; i++) { + if (_params[i] == NULL) { + break; + } + + // label before or after, this could probably be done via floats however + String pitem; + switch (_params[i]->getLabelPlacement()) { + case WFM_LABEL_BEFORE: + pitem = FPSTR(HTTP_FORM_LABEL); + pitem += FPSTR(HTTP_FORM_PARAM); + break; + case WFM_LABEL_AFTER: + pitem = FPSTR(HTTP_FORM_PARAM); + pitem += FPSTR(HTTP_FORM_LABEL); + break; + default: + // WFM_NO_LABEL + pitem = FPSTR(HTTP_FORM_PARAM); + break; + } + + // if no ID use customhtml for item, else generate from param string + if (_params[i]->getID() != NULL) { + if(tok_i)pitem.replace("{i}", _params[i]->getID()); + if(tok_n)pitem.replace("{n}", _params[i]->getID()); + if(tok_p)pitem.replace("{p}", "{t}"); + if(tok_t)pitem.replace("{t}", _params[i]->getPlaceholder()); + snprintf(parLength, 5, "%d", _params[i]->getValueLength()); + if(tok_l)pitem.replace("{l}", parLength); + if(tok_v)pitem.replace("{v}", _params[i]->getValue()); + if(tok_c)pitem.replace("{c}", _params[i]->getCustomHTML()); // meant for additional attributes, not html + } else { + pitem = _params[i]->getCustomHTML(); + } + + page += pitem; + } + if (_params[0] != NULL) { + page += FPSTR(HTTP_FORM_PARAM_END); + } + } + + return page; +} + +/** + * HTTPD CALLBACK save form and redirect to WLAN config page again + */ void WiFiManager::handleWifiSave() { - DEBUG_WM(F("WiFi save")); + DEBUG_WM(F("HTTP WiFi save")); //SAVE/connect here _ssid = server->arg("s").c_str(); @@ -593,7 +820,7 @@ void WiFiManager::handleWifiSave() { //read parameter String value = server->arg(_params[i]->getID()).c_str(); //store it in array - value.toCharArray(_params[i]->_value, _params[i]->_length); + value.toCharArray(_params[i]->_value, _params[i]->_length+1); // length+1 null terminated DEBUG_WM(F("Parameter")); DEBUG_WM(_params[i]->getID()); DEBUG_WM(value); @@ -628,6 +855,7 @@ void WiFiManager::handleWifiSave() { page += FPSTR(HTTP_SAVED); page += FPSTR(HTTP_END); + server->sendHeader("Content-Length", String(page.length())); server->send(200, "text/html", page); DEBUG_WM(F("Sent wifi save page")); @@ -635,9 +863,11 @@ void WiFiManager::handleWifiSave() { connect = true; //signal ready to connect/reset } -/** Handle the info page */ +/** + * HTTPD CALLBACK info page + */ void WiFiManager::handleInfo() { - DEBUG_WM(F("Info")); + DEBUG_WM(F("HTTP Info")); String page = FPSTR(HTTP_HEAD); page.replace("{v}", "Info"); @@ -645,66 +875,257 @@ void WiFiManager::handleInfo() { page += FPSTR(HTTP_STYLE); page += _customHeadElement; page += FPSTR(HTTP_HEAD_END); - page += F("
"); + + reportStatus(page); + + // @todo add versioning here + + page += F("

esp8266


"); + + // subject to rollover! + page += F("
Uptime
"); + page += (String)(system_get_time() / 1000000 / 60) + " mins "; + page += (String)((system_get_time() / 1000000)%60) + " secs"; + page += F("
Chip ID
"); page += ESP.getChipId(); page += F("
"); + page += F("
Flash Chip ID
"); page += ESP.getFlashChipId(); page += F("
"); + page += F("
IDE Flash Size
"); page += ESP.getFlashChipSize(); page += F(" bytes
"); - page += F("
Real Flash Size
"); + + page += F("
Actual Flash Size
"); page += ESP.getFlashChipRealSize(); page += F(" bytes
"); - page += F("
Soft AP IP
"); + + page += F("
SDK Version
"); + page += system_get_sdk_version(); + page += F("
"); + + page += F("
Core Version
"); + page += ESP.getCoreVersion(); + page += F("
"); + + page += F("
Boot Version
"); + page += system_get_boot_version(); + page += F("
"); + + page += F("
CPU Frequency
"); + page += ESP.getCpuFreqMHz(); + page += F("MHz
"); + + // heap is dynamic, would have to have a known starting for graph + // uint32_t heapstart = 47000; + // page += F("
Memory Heap
"); + // page += F("Used/Total bytes
"); + // page += (heapstart)-ESP.getFreeHeap(); + // page += "/"; + // page += (heapstart); + // page += "
"; + // page += F("
"); + + page += F("
Memory - Free Heap
"); + page += ESP.getFreeHeap(); + page += F(" bytes available
"); + + page += F("
Memory - Sketch Size
"); + page += F("Used/Total bytes
"); + page += (ESP.getSketchSize()+ESP.getFreeSketchSpace())-ESP.getFreeSketchSpace(); + page += "/"; + page += (ESP.getSketchSize()+ESP.getFreeSketchSpace()); + page += F("
"); + + page += F("
Last reset reason
"); + page += ESP.getResetReason(); + page += F("
"); + + page += F("

WiFi


Access Point IP
"); page += WiFi.softAPIP().toString(); page += F("
"); - page += F("
Soft AP MAC
"); + + page += F("
Access Point MAC
"); page += WiFi.softAPmacAddress(); page += F("
"); + + page += F("
SSID
"); + page += WiFi.SSID(); + page += F("
"); + + page += F("
BSSID
"); + page += WiFi.BSSIDstr(); + page += F("
"); + + page += F("
Station IP
"); + page += WiFi.localIP().toString(); + page += F("
"); + + page += F("
Station Gateway
"); + page += WiFi.gatewayIP().toString(); + page += F("
"); + + page += F("
Station Subnet
"); + page += WiFi.subnetMask().toString(); + page += F("
"); + + page += F("
DNS Server
"); + page += WiFi.dnsIP().toString(); + page += F("
"); + + page += F("
Hostname
"); + page += WiFi.hostname(); + page += F("
"); + page += F("
Station MAC
"); page += WiFi.macAddress(); page += F("
"); + + page += F("
Connected
"); + page += WiFi.isConnected() ? "Yes" : "No"; + page += F("
"); + + page += F("
Autoconnect
"); + page += WiFi.getAutoConnect() ? "Enabled" : "Disabled"; + page += F("
"); page += F("
"); + + page += "
"; + + page += F("

Available Pages


"); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + // @todo add callback here + page += F("
PageFunction
/Menu page.
/wifiShow WiFi scan results and enter WiFi configuration.
/wifisaveSave WiFi configuration information and configure device. Needs variables supplied.
/closeClose the configuration server and configuration WiFi network.
/iInformation page
/rDelete WiFi configuration and reboot. ESP device will not reconnect to a network until new WiFi configuration data is entered.
"); + page += F("

More information about WiFiManager at https://github.com/tzapu/WiFiManager."); page += FPSTR(HTTP_END); + server->sendHeader("Content-Length", String(page.length())); server->send(200, "text/html", page); DEBUG_WM(F("Sent info page")); } -/** Handle the reset page */ -void WiFiManager::handleReset() { - DEBUG_WM(F("Reset")); + +/** + * HTTPD CALLBACK root or redirect to captive portal + */ +void WiFiManager::handleExit() { + DEBUG_WM(F("<- HTTP Exit")); String page = FPSTR(HTTP_HEAD); - page.replace("{v}", "Info"); + page.replace("{v}", "Exit"); + page += FPSTR(HTTP_SCRIPT); + page += FPSTR(HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(HTTP_HEAD_END); + + page += "Exiting"; + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + abort = true; +} + +void WiFiManager::reportStatus(String &page){ + + if (WiFi.SSID() != ""){ + if (WiFi.status()==WL_CONNECTED){ + page += "

"; + page += F("Connected to "); + page += WiFi.SSID(); + page += F("
with IP "); + page += WiFi.localIP().toString(); + page += F(""); + } else { + page += "
"; + page += "Not Connected to "; + page += WiFi.SSID(); + } + } else { + page += "
"; + page += "No AP set"; + } + page += "
"; +} + +/** + * HTTPD CALLBACK reset page + */ +void WiFiManager::handleReset() { + DEBUG_WM(F("<- HTTP Reset")); + + String page = FPSTR(HTTP_HEAD); + page.replace("{v}", "Reset"); page += FPSTR(HTTP_SCRIPT); page += FPSTR(HTTP_STYLE); page += _customHeadElement; page += FPSTR(HTTP_HEAD_END); page += F("Module will reset in a few seconds."); page += FPSTR(HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); server->send(200, "text/html", page); - DEBUG_WM(F("Sent reset page")); - delay(5000); + DEBUG_WM(F("RESETTING ESP")); + delay(1000); ESP.reset(); - delay(2000); } +/** + * HTTPD CALLBACK erase page + */ +void WiFiManager::handleErase() { + DEBUG_WM(F("<- HTTP Erase")); + String page = FPSTR(HTTP_HEAD); + page.replace("{v}", "Erase"); + page += FPSTR(HTTP_SCRIPT); + page += FPSTR(HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(HTTP_HEAD_END); -//removed as mentioned here https://github.com/tzapu/WiFiManager/issues/114 -/*void WiFiManager::handle204() { - DEBUG_WM(F("204 No Response")); - server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - server->sendHeader("Pragma", "no-cache"); - server->sendHeader("Expires", "-1"); - server->send ( 204, "text/plain", ""); -}*/ + bool ret = ESP_eraseConfig(); + if(!ret) DEBUG_WM(F("ERASE FAILED")); + + // bool ret = reset = true; + + if(ret) page += F("Module will reset in a few seconds."); + else { + page += F("An Error Occured"); + DEBUG_WM(F("WiFi.eraseConfig reported an error")); + } + + page += FPSTR(HTTP_END); + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + delay(2000); + + if(ret){ + DEBUG_WM(F("RESETTING ESP")); + ESP.reset(); + } +} void WiFiManager::handleNotFound() { if (captivePortal()) { // If captive portal redirect instead of displaying the error page. @@ -725,14 +1146,22 @@ void WiFiManager::handleNotFound() { server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server->sendHeader("Pragma", "no-cache"); server->sendHeader("Expires", "-1"); + server->sendHeader("Content-Length", String(message.length())); server->send ( 404, "text/plain", message ); } - -/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */ +/** + * HTTPD redirector + * Redirect to captive portal if we got a request for another domain. + * Return true in that case so the page handler do not try to handle the request again. + */ boolean WiFiManager::captivePortal() { - if (!isIp(server->hostHeader()) ) { - DEBUG_WM(F("Request redirected to captive portal")); + DEBUG_WM("-> " + server->hostHeader()); + + if(!_enableCaptivePortal) return false; // skip redirections + + if (!isIp(server->hostHeader())) { + DEBUG_WM(F("<- Request redirected to captive portal")); server->sendHeader("Location", String("http://") + toStringIp(server->client().localIP()), true); server->send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. server->client().stop(); // Stop is needed because we sent no content length @@ -741,6 +1170,8 @@ boolean WiFiManager::captivePortal() { return false; } +// MORE SETTERS + //start up config portal callback void WiFiManager::setAPCallback( void (*func)(WiFiManager* myWiFiManager) ) { _apcallback = func; @@ -761,16 +1192,87 @@ void WiFiManager::setRemoveDuplicateAPs(boolean removeDuplicates) { _removeDuplicateAPs = removeDuplicates; } +//if this is true, remove duplicated Access Points - defaut true +void WiFiManager::setConfigPortalBlocking(boolean shoudlBlock) { + _configPortalIsBlocking = shoudlBlock; +} +//setter for ESP wifi.persistent so we can remember it and restore user preference, as WIFi._persistent is protected +void WiFiManager::setRestorePersistent(boolean persistent) { + _userpersistent = persistent; + if(!persistent) DEBUG_WM(F("persistent is off")); +} + +void WiFiManager::setShowStaticFields(boolean alwaysShow){ + _sta_show_static_fields = alwaysShow; +} + +void WiFiManager::setCaptivePortalEnable(boolean enabled){ + _enableCaptivePortal = enabled; +} + +// HELPERS template void WiFiManager::DEBUG_WM(Generic text) { if (_debug) { - Serial.print("*WM: "); - Serial.println(text); + _debugPort.print("*WM: "); + _debugPort.print(text); + _debugPort.print("\n"); } } +template +void WiFiManager::DEBUG_WM(Generic text,Genericb textb) { + if (_debug) { + _debugPort.print("*WM: "); + _debugPort.print(text); + _debugPort.print(" "); + _debugPort.print(textb); + _debugPort.print("\n"); + } +} + +/* + String WiFiManager::getSSID() { + if (_ssid == "") { + DEBUG_WM(F("Reading SSID")); + _ssid = WiFi.SSID(); + DEBUG_WM(F("SSID: ")); + DEBUG_WM(_ssid); + } + return _ssid; + } + + String WiFiManager::getPassword() { + if (_pass == "") { + DEBUG_WM(F("Reading Password")); + _pass = WiFi.psk(); + DEBUG_WM("Password: " + _pass); + //DEBUG_WM(_pass); + } + return _pass; + } +*/ + +void WiFiManager::debugSoftAPConfig(){ + softap_config config; + wifi_softap_get_config(&config); + + DEBUG_WM(F("SoftAP Configuration")); + DEBUG_WM(F("--------------------")); + DEBUG_WM(F("ssid: "),(char *) config.ssid); + DEBUG_WM(F("password: "),(char *) config.password); + DEBUG_WM(F("ssid_len: "),config.ssid_len); + DEBUG_WM(F("channel: "),config.channel); + DEBUG_WM(F("authmode: "),config.authmode); + DEBUG_WM(F("ssid_hidden: "),config.ssid_hidden); + DEBUG_WM(F("max_connection: "),config.max_connection); + DEBUG_WM(F("beacon_interval: "),(String)config.beacon_interval + "(ms)"); + DEBUG_WM(F("--------------------")); +} + + int WiFiManager::getRSSIasQuality(int RSSI) { int quality = 0; @@ -804,3 +1306,117 @@ String WiFiManager::toStringIp(IPAddress ip) { res += String(((ip >> 8 * 3)) & 0xFF); return res; } + +boolean WiFiManager::validApPassword(){ + // check that ap password is valid, return false + if (_apPassword == NULL) _apPassword = ""; + if (_apPassword != "") { + if (_apPassword.length() < 8 || _apPassword.length() > 63) { + DEBUG_WM(F("AccessPoint set password is INVALID")); + _apPassword = ""; + return false; // @todo FATAL or fallback to empty ? + } + DEBUG_WM(F("AccessPoint set password is VALID")); + DEBUG_WM(_apPassword); + } + return true; +} + +String WiFiManager::getWLStatusString(uint8_t status){ + const char * const WIFI_STA_STATUS[] + { + "WL_IDLE_STATUS", + "WL_NO_SSID_AVAIL", + "WL_SCAN_COMPLETED", + "WL_CONNECTED", + "WL_CONNECT_FAILED", + "WL_CONNECTION_LOST", + "WL_DISCONNECTED" + }; + + if(status >=0 && status <=6) return WIFI_STA_STATUS[status]; + return "UNKNOWN"; +} + +String WiFiManager::encryptionTypeStr(uint8_t authmode) { + switch(authmode) { + case ENC_TYPE_NONE: + return "None"; + case ENC_TYPE_WEP: + return "WEP"; + case ENC_TYPE_TKIP: + return "WPA_PSK"; + case ENC_TYPE_CCMP: + return "WPA2_PSK"; + case ENC_TYPE_AUTO: + return "WPA_WPA2_PSK"; + default: + return "Unknown"; + } +} + +// set mode ignores WiFi.persistent +bool WiFiManager::WiFi_Mode(WiFiMode_t m,bool persistent) { + if((wifi_get_opmode() == (uint8) m ) && !persistent) { + return true; + } + bool ret; + ETS_UART_INTR_DISABLE(); + if(persistent) ret = wifi_set_opmode(m); + else ret = wifi_set_opmode_current(m); + ETS_UART_INTR_ENABLE(); + + return ret; +} +bool WiFiManager::WiFi_Mode(WiFiMode_t m) { + return WiFi_Mode(m,false); +} + +// sta disconnect without persistent +bool WiFiManager::WiFi_Disconnect() { + if((WiFi.getMode() & WIFI_STA) != 0) { + bool ret; + DEBUG_WM(F("wifi station disconnect")); + ETS_UART_INTR_DISABLE(); + ret = wifi_station_disconnect(); + ETS_UART_INTR_ENABLE(); + return ret; + } +} + +// toggle STA without persistent +bool WiFiManager::WiFi_enableSTA(bool enable,bool persistent) { + + WiFiMode_t currentMode = WiFi.getMode(); + bool isEnabled = ((currentMode & WIFI_STA) != 0); + + if((isEnabled != enable) || persistent) { + if(enable) { + if(persistent) DEBUG_WM(F("enableSTA PERSISTENT ON")); + return WiFi_Mode((WiFiMode_t)(currentMode | WIFI_STA),persistent); + } else { + return WiFi_Mode((WiFiMode_t)(currentMode & (~WIFI_STA)),persistent); + } + } else { + return true; + } +} +bool WiFiManager::WiFi_enableSTA(bool enable) { + return WiFi_enableSTA(enable,false); +} + +// erase config BUG polyfill +// https://github.com/esp8266/Arduino/pull/3635 +bool WiFiManager::ESP_eraseConfig(void) { + // return ESP.eraseConfig(); + + const size_t cfgSize = 0x4000; + size_t cfgAddr = ESP.getFlashChipSize() - cfgSize; + + for (size_t offset = 0; offset < cfgSize; offset += SPI_FLASH_SEC_SIZE) { + if (!ESP.flashEraseSector((cfgAddr + offset) / SPI_FLASH_SEC_SIZE)) { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/WiFiManager.h b/WiFiManager.h index 00e8bd3..826be09 100644 --- a/WiFiManager.h +++ b/WiFiManager.h @@ -5,7 +5,7 @@ inspired by: http://www.esp8266.com/viewtopic.php?f=29&t=2520 https://github.com/chriscook8/esp-arduino-apboot - https://github.com/esp8266/Arduino/tree/esp8266/hardware/esp8266com/esp8266/libraries/DNSServer/examples/CaptivePortalAdvanced + https://github.com/esp8266/Arduino/tree/master/libraries/DNSServer/examples/CaptivePortalAdvanced Built by AlexT https://github.com/tzapu Licensed under MIT license **************************************************************/ @@ -22,21 +22,43 @@ extern "C" { #include "user_interface.h" } -const char HTTP_HEAD[] PROGMEM = "{v}"; -const char HTTP_STYLE[] PROGMEM = ""; +const char HTTP_HEAD[] PROGMEM = "{v}"; +// const char HTTP_STYLE[] PROGMEM = ""; +const char HTTP_STYLE[] PROGMEM = ""; + const char HTTP_SCRIPT[] PROGMEM = ""; -const char HTTP_HEAD_END[] PROGMEM = "
"; -const char HTTP_PORTAL_OPTIONS[] PROGMEM = "



"; -const char HTTP_ITEM[] PROGMEM = "
{v} {r}%
"; -const char HTTP_FORM_START[] PROGMEM = "


"; -const char HTTP_FORM_LABEL[] PROGMEM = ""; -const char HTTP_FORM_PARAM[] PROGMEM = "
"; -const char HTTP_FORM_END[] PROGMEM = "
"; -const char HTTP_SCAN_LINK[] PROGMEM = "
"; -const char HTTP_SAVED[] PROGMEM = "
Credentials Saved
Trying to connect ESP to network.
If it fails reconnect to AP to try again
"; +const char HTTP_HEAD_END[] PROGMEM = "
"; +const char HTTP_PORTAL_OPTIONS[] PROGMEM = "




"; +const char HTTP_ITEM[] PROGMEM = ""; +// const char HTTP_ITEM[] PROGMEM = "
{v} {R} {r}% {q} {e}
"; // test all tokens +//const char HTTP_ITEM_PADLOCK[] PROGMEM = ""; +const char HTTP_FORM_START[] PROGMEM = "


"; +const char HTTP_FORM_END[] PROGMEM = "
"; +const char HTTP_FORM_LABEL[] PROGMEM = ""; +const char HTTP_FORM_PARAM_START[] PROGMEM = "
"; +const char HTTP_FORM_PARAM[] PROGMEM = "
"; +const char HTTP_FORM_PARAM_END[] PROGMEM = "
"; +const char HTTP_SCAN_LINK[] PROGMEM = "
"; +const char HTTP_SAVED[] PROGMEM = "
Saving Credentials
Trying to connect ESP to network.
If it fails reconnect to AP to try again
"; const char HTTP_END[] PROGMEM = "
"; -#define WIFI_MANAGER_MAX_PARAMS 20 +#define WIFI_MANAGER_MAX_PARAMS 10 #define WFM_LABEL_BEFORE 1 #define WFM_LABEL_AFTER 2 @@ -73,30 +95,34 @@ class WiFiManagerParameter { class WiFiManager { public: + WiFiManager(Stream& consolePort); WiFiManager(); + ~WiFiManager(); - boolean autoConnect(boolean showStaticIPFields = false); - boolean autoConnect(char const *apName, char const *apPassword = NULL, boolean showStaticIPFields = false); + boolean autoConnect(); + boolean autoConnect(char const *apName, char const *apPassword = NULL); //if you want to always start the config portal, without trying to connect first boolean startConfigPortal(); boolean startConfigPortal(char const *apName, char const *apPassword = NULL); + boolean process(); + void startWebPortal(); + void stopWebPortal(); // get the AP name of the config portal, so it can be used in the callback String getConfigPortalSSID(); void resetSettings(); - //sets timeout before webserver loop ends and exits even if there has been no setup. - //usefully for devices that failed to connect at some point and got stuck in a webserver loop + //sets timeout before AP,webserver loop ends and exits even if there has been no setup. + //useful for devices that failed to connect at some point and got stuck in a webserver loop //in seconds setConfigPortalTimeout is a new name for setTimeout void setConfigPortalTimeout(unsigned long seconds); - void setTimeout(unsigned long seconds); + void setTimeout(unsigned long seconds); // @deprecated, alias - //sets timeout for which to attempt connecting, usefull if you get a lot of failed connects + //sets timeout for which to attempt connecting, useful if you get a lot of failed connects void setConnectTimeout(unsigned long seconds); - void setDebugOutput(boolean debug); //defaults to not showing anything under 8% signal quality if called void setMinimumSignalQuality(int quality = 8); @@ -110,14 +136,22 @@ class WiFiManager void setSaveConfigCallback( void (*func)(void) ); //adds a custom parameter void addParameter(WiFiManagerParameter *p); - //if this is set, it will exit after config, even if connection is unsucessful. + //if this is set, it will exit after config, even if connection is unsuccessful. void setBreakAfterConfig(boolean shouldBreak); - //if this is set, try WPS setup when starting (this will delay config portal for up to 2 mins) - //TODO + //if this is set, portal will be blocking and wait until save or exit, is false user must manually process to handle config portal + void setConfigPortalBlocking(boolean shouldBlock); + + //if this is set, customise style void setCustomHeadElement(const char* element); //if this is true, remove duplicated Access Points - defaut true void setRemoveDuplicateAPs(boolean removeDuplicates); + //setter for ESP wifi.persistent so we can remember it and restore user preference, as WIFi._persistent is protected + void setRestorePersistent(boolean persistent); + //if true, always show static net inputs, IP, subnet, gateway, if false, only show when already set. + void setShowStaticFields(boolean alwaysShow); + //if true disable captive portal redirection + void setCaptivePortalEnable(boolean enabled); private: std::unique_ptr dnsServer; @@ -130,14 +164,23 @@ class WiFiManager void setupConfigPortal(); void startWPS(); + bool startAP(); - const char* _apName = "no-net"; - const char* _apPassword = NULL; + // typedef enum WiFiMode + // { + // WIFI_OFF = 0, WIFI_STA = 1, WIFI_AP = 2, WIFI_AP_STA = 3 + // } WiFiMode_t; + + String _apName = "no-net"; + String _apPassword = ""; String _ssid = ""; String _pass = ""; unsigned long _configPortalTimeout = 0; unsigned long _connectTimeout = 0; unsigned long _configPortalStart = 0; + boolean _userpersistent = true; + WiFiMode_t _usermode = WIFI_OFF; + boolean _enableCaptivePortal = true; IPAddress _ap_static_ip; IPAddress _ap_static_gw; @@ -147,11 +190,13 @@ class WiFiManager IPAddress _sta_static_sn; boolean _sta_show_static_fields = false; + // option parameters int _paramsCount = 0; int _minimumQuality = -1; boolean _removeDuplicateAPs = true; boolean _shouldBreakAfterConfig = false; boolean _tryWPS = false; + boolean _configPortalIsBlocking = true; const char* _customHeadElement = ""; @@ -161,6 +206,8 @@ class WiFiManager int status = WL_IDLE_STATUS; int connectWifi(String ssid, String pass); uint8_t waitForConnectResult(); + uint8_t waitForConnectResult(uint16_t timeout); + String getWLStatusString(uint8_t status); void handleRoot(); void handleWifi(boolean scan); @@ -168,9 +215,24 @@ class WiFiManager void handleInfo(); void handleReset(); void handleNotFound(); - void handle204(); + void handleExit(); + void handleErase(); + boolean captivePortal(); boolean configPortalHasTimeout(); + boolean stopConfigPortal(); + uint8_t handleConfigPortal(); + bool WiFi_Mode(WiFiMode_t m); + bool WiFi_Mode(WiFiMode_t m,bool persistent); + bool WiFi_Disconnect(); + bool WiFi_enableSTA(bool enable); + bool WiFi_enableSTA(bool enable,bool persistent); + bool ESP_eraseConfig(); + void debugSoftAPConfig(); + + String getParamOut(); + String getScanItemOut(); + String getStaticOut(); // DNS server const byte DNS_PORT = 53; @@ -179,17 +241,31 @@ class WiFiManager int getRSSIasQuality(int RSSI); boolean isIp(String str); String toStringIp(IPAddress ip); + boolean validApPassword(); + String encryptionTypeStr(uint8_t authmode); + void reportStatus(String &page); boolean connect; - boolean _debug = true; + boolean abort; + boolean reset = false; + boolean configPortalActive = false; + boolean webPortalActive = false; + boolean _debug = true; + boolean portalTimeoutResult = false; + boolean portalAbortResult = false; + boolean storeSTAmode = true; // option store persistent STA mode in connectwifi + int timer = 0; void (*_apcallback)(WiFiManager*) = NULL; void (*_savecallback)(void) = NULL; + Stream& _debugPort; // debug output stream ref WiFiManagerParameter* _params[WIFI_MANAGER_MAX_PARAMS]; template void DEBUG_WM(Generic text); + template + void DEBUG_WM(Generic text,Genericb textb); template auto optionalIPFromString(T *obj, const char *s) -> decltype( obj->fromString(s) ) { diff --git a/examples/AutoConnectWithFSParameters/AutoConnectWithFSParameters.ino b/examples/AutoConnectWithFSParameters/AutoConnectWithFSParameters.ino index 1d58e5c..1891808 100644 --- a/examples/AutoConnectWithFSParameters/AutoConnectWithFSParameters.ino +++ b/examples/AutoConnectWithFSParameters/AutoConnectWithFSParameters.ino @@ -102,7 +102,7 @@ void setup() { //sets timeout until configuration portal gets turned off //useful to make it all retry or go to sleep //in seconds - //wifiManager.setTimeout(120); + //wifiManager.setConfigPortalTimeout(120); //fetches ssid and pass and tries to connect //if it does not connect it starts an access point with the specified name diff --git a/examples/AutoConnectWithFSParametersAndCustomIP/AutoConnectWithFSParametersAndCustomIP.ino b/examples/AutoConnectWithFSParametersAndCustomIP/AutoConnectWithFSParametersAndCustomIP.ino index 3f391ae..135b99f 100644 --- a/examples/AutoConnectWithFSParametersAndCustomIP/AutoConnectWithFSParametersAndCustomIP.ino +++ b/examples/AutoConnectWithFSParametersAndCustomIP/AutoConnectWithFSParametersAndCustomIP.ino @@ -129,7 +129,7 @@ void setup() { //sets timeout until configuration portal gets turned off //useful to make it all retry or go to sleep //in seconds - //wifiManager.setTimeout(120); + //wifiManager.setConfigPortalTimeout(120); //fetches ssid and pass and tries to connect //if it does not connect it starts an access point with the specified name diff --git a/examples/AutoConnectWithTimeout/AutoConnectWithTimeout.ino b/examples/AutoConnectWithTimeout/AutoConnectWithTimeout.ino index c90cf10..da77cbe 100644 --- a/examples/AutoConnectWithTimeout/AutoConnectWithTimeout.ino +++ b/examples/AutoConnectWithTimeout/AutoConnectWithTimeout.ino @@ -20,7 +20,7 @@ void setup() { //sets timeout until configuration portal gets turned off //useful to make it all retry or go to sleep //in seconds - wifiManager.setTimeout(180); + wifiManager.setConfigPortalTimeout(180); //fetches ssid and pass and tries to connect //if it does not connect it starts an access point with the specified name diff --git a/examples/AutoConnectWithTimeout/platformio.ini b/examples/AutoConnectWithTimeout/platformio.ini new file mode 100644 index 0000000..7e80288 --- /dev/null +++ b/examples/AutoConnectWithTimeout/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:nodemcuv2] +platform=espressif8266 +board=nodemcuv2 +framework=arduino + +[platformio] +src_dir=AutoConnectWithTimeout +lib_dir=/Users/shawn/projects/microcontrollers/dev/libraries diff --git a/examples/OnDemandConfigPortal/OnDemandConfigPortal.ino b/examples/OnDemandConfigPortal/OnDemandConfigPortal.ino index edf4f46..1292c35 100644 --- a/examples/OnDemandConfigPortal/OnDemandConfigPortal.ino +++ b/examples/OnDemandConfigPortal/OnDemandConfigPortal.ino @@ -5,7 +5,7 @@ #include #include //https://github.com/tzapu/WiFiManager -// select wich pin will trigger the configuraton portal when set to LOW +// select which pin will trigger the configuration portal when set to LOW // ESP-01 users please note: the only pins available (0 and 2), are shared // with the bootloader, so always set them HIGH at power-up #define TRIGGER_PIN 0 @@ -33,7 +33,7 @@ void loop() { //sets timeout until configuration portal gets turned off //useful to make it all retry or go to sleep //in seconds - //wifiManager.setTimeout(120); + //wifiManager.setConfigPortalTimeout(120); //it starts an access point with the specified name //here "AutoConnectAP" @@ -42,6 +42,9 @@ void loop() { //WITHOUT THIS THE AP DOES NOT SEEM TO WORK PROPERLY WITH SDK 1.5 , update to at least 1.5.1 //WiFi.mode(WIFI_STA); + // disable captive portal redirection + // wifiManager.setCaptivePortalEnable(false); + if (!wifiManager.startConfigPortal("OnDemandAP")) { Serial.println("failed to connect and hit timeout"); delay(3000); diff --git a/examples/OnDemandWebPortal/OnDemandWebPortal.ino b/examples/OnDemandWebPortal/OnDemandWebPortal.ino new file mode 100644 index 0000000..50f5657 --- /dev/null +++ b/examples/OnDemandWebPortal/OnDemandWebPortal.ino @@ -0,0 +1,57 @@ +/** + * OnDemandWebPortal.ino + * example of running the webportal manually, independantly from the captiveportal + * trigger pin will start a webportal for 120 seconds then turn it off. + * + */ + +#include //https://github.com/esp8266/Arduino + +//needed for library +#include +#include +#include //https://github.com/tzapu/WiFiManager + +// select which pin will trigger the configuration portal when set to LOW +// ESP-01 users please note: the only pins available (0 and 2), are shared +// with the bootloader, so always set them HIGH at power-up +#define TRIGGER_PIN 0 + +// set your ap credentials +const char *ssid = "********"; +const char *password = "********"; + +int timeout = 120; // seconds + +WiFiManager wifiManager; + +bool webportalrunning = false; +int start = millis(); + +void setup() { + // put your setup code here, to run once: + Serial.begin(115200); + Serial.println("\n Starting"); + pinMode(TRIGGER_PIN, INPUT_PULLUP); +} + + +void loop() { + + if(webportalrunning){ + wifiManager.process(); + if(millis()-start > timeout*1000){ + Serial.println("webportaltimeout"); + webportalrunning = false; + wifiManager.stopWebPortal(); // auto off + } + } + + // is configuration portal requested? + if ( digitalRead(TRIGGER_PIN) == LOW && !webportalrunning) { + wifiManager.startWebPortal(); + webportalrunning = true; + start = millis(); + } + // put your main code here, to run repeatedly: +} diff --git a/extras/WiFiManager.template.html b/extras/WiFiManager.template.html index e15593b..09ae94b 100644 --- a/extras/WiFiManager.template.html +++ b/extras/WiFiManager.template.html @@ -6,15 +6,132 @@ {v} - + + + + + + + + + + + +