/************************************************************** WiFiManager is a library for the ESP8266/Arduino platform (https://github.com/esp8266/Arduino) to enable easy configuration and reconfiguration of WiFi credentials using a Captive Portal 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 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; _labelPlacement = WFM_LABEL_BEFORE; _customHTML = custom; } WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length) { init(id, placeholder, defaultValue, length, "", WFM_LABEL_BEFORE); } WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom) { init(id, placeholder, defaultValue, length, custom, WFM_LABEL_BEFORE); } 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; _labelPlacement = labelPlacement; _value = new char[length + 1]; for (int i = 0; i < length; i++) { _value[i] = 0; } if (defaultValue != NULL) { strncpy(_value, defaultValue, length); } _customHTML = custom; } const char* WiFiManagerParameter::getValue() { return _value; } const char* WiFiManagerParameter::getID() { return _id; } const char* WiFiManagerParameter::getPlaceholder() { return _placeholder; } int WiFiManagerParameter::getValueLength() { return _length; } int WiFiManagerParameter::getLabelPlacement() { return _labelPlacement; } const char* WiFiManagerParameter::getCustomHTML() { return _customHTML; } 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; } _params[_paramsCount] = p; _paramsCount++; DEBUG_WM("Adding parameter"); DEBUG_WM(p->getID()); } // constructor WiFiManager::WiFiManager() { WiFi.persistent(false); } // destructor WiFiManager::~WiFiManager() { WiFi.persistent(true); // @todo restore original } bool WiFiManager::startAP(){ DEBUG_WM(F("Configuring access point with with SSID ... ")); 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); } bool ret = true; // start soft AP with pass or anonymous if (_apPassword != NULL) { ret = WiFi.softAP(_apName, _apPassword);//password option } else { DEBUG_WM(F("AP has anonymous access")); ret = WiFi.softAP(_apName); } delay(500); // slight delay to make sure we have an AP IP DEBUG_WM(F("AP IP address: ")); DEBUG_WM(WiFi.softAPIP()); return ret; } void WiFiManager::setupConfigPortal() { // setup dns and web servers dnsServer.reset(new DNSServer()); server.reset(new ESP8266WebServer(80)); DEBUG_WM(F("setupConfigPortal")); _configPortalStart = millis(); if(!startAP()) DEBUG_WM("There was an error stating the AP"); /* Setup the DNS server redirecting all the domains to the apIP */ dnsServer->setErrorReplyCode(DNSReplyCode::NoError); dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); /* 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("/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() { String ssid = "ESP" + String(ESP.getChipId()); return autoConnect(ssid.c_str(), NULL); } boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) { DEBUG_WM(F("")); DEBUG_WM(F("AutoConnect")); // read eeprom for ssid and pass, deprecated //String ssid = getSSID(); //String pass = getPassword(); // attempt to connect using saved settings, on fail fallback to AP config portal // WiFi.mode(WIFI_STA); // why, should not be needed if (WiFi.status() == WL_CONNECTED || connectWifi("", "") == WL_CONNECTED) { //connected DEBUG_WM(F("IP Address:")); DEBUG_WM(WiFi.localIP()); 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() { String ssid = "ESP" + String(ESP.getChipId()); return startConfigPortal(ssid.c_str(), NULL); } boolean WiFiManager::startConfigPortal(char const *apName, char const *apPassword) { //setup AP // @todo AP_STA will constantly try to connect to old aps in background, if not allowing background configportal , maybe turn off STA WiFi.mode(WIFI_AP); // WiFi.mode(WIFI_AP_STA); DEBUG_WM("SET AP STA"); _apName = apName; _apPassword = apPassword; if(!validApPassword()) return false; // do AP callback if set if ( _apcallback != NULL) { _apcallback(this); } connect = false; // global // init configportal setupConfigPortal(); uint8_t ret; // blocking loop waiting for config while(1){ // check if timed out if(configPortalHasTimeout()){ stopConfigPortal(); return WiFi.status() == WL_CONNECTED; //returns connected bool, ok I guess } ret = handleConfigPortal(); if(ret != WL_IDLE_STATUS) return ret == WL_CONNECTED; yield(); } return false; } //@todo need enums for return, using 3 esp enums for now uint8_t WiFiManager::handleConfigPortal(){ //DNS handler dnsServer->processNextRequest(); //HTTP handler server->handleClient(); // Waiting for save... if (connect) { connect = false; delay(2000); DEBUG_WM(F("Connecting to new AP")); // 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) { // this is more of an exiting callback than a save stopConfigPortal(); // fail return WL_CONNECT_FAILED; } } return WL_IDLE_STATUS; } boolean WiFiManager::stopConfigPortal(){ // do save callback if ( _savecallback != NULL) { //todo: confirm or verify data was saved _savecallback(); } // @todo does this unload them enough ? server.reset(); dnsServer.reset(); // turn off AP DEBUG_WM(F("disconnect configportal")); WiFi.softAPdisconnect(); return false; } // @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...")); // 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 // @todo this had to be fixed, it will not let you set a new wifi, if still connected to another, as it just returns if (WiFi.status() == WL_CONNECTED && (ssid=="" || WiFi.SSID() == ssid)) { DEBUG_WM("Already connected. Exiting"); return WL_CONNECTED; } // if ssid argument provided connect to that if (ssid != "") { DEBUG_WM("Connecting to new AP"); WiFi.persistent(true); WiFi.begin(ssid.c_str(), pass.c_str()); WiFi.persistent(false); } else { // connect using saved ssid if there is one if (WiFi.SSID()) { DEBUG_WM("Connecting to saved AP"); // trying to fix connection in progress hanging, explain why ?? ETS_UART_INTR_DISABLE(); wifi_station_disconnect(); ETS_UART_INTR_ENABLE(); WiFi.begin(); } else { DEBUG_WM("No saved credentials"); } } int connRes = waitForConnectResult(); DEBUG_WM ("Connection result: "); DEBUG_WM ( 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 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) return WiFi.waitForConnectResult(); DEBUG_WM (F("connectTimeout set, waiting for connect....")); uint8_t status; int timeout = millis() + _connectTimeout; while(millis() < timeout) { status = WiFi.status(); if (status == WL_CONNECTED || status == WL_CONNECT_FAILED) { return status; } DEBUG_WM ("."); delay(100); } return status; } void WiFiManager::startWPS() { DEBUG_WM("START WPS"); 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 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.disconnect(true); //delay(200); } void WiFiManager::setTimeout(unsigned long seconds) { setConfigPortalTimeout(seconds); } void WiFiManager::setConfigPortalTimeout(unsigned long seconds) { _configPortalTimeout = seconds * 1000; } void WiFiManager::setConnectTimeout(unsigned long seconds) { _connectTimeout = seconds * 1000; } void WiFiManager::setDebugOutput(boolean debug) { _debug = debug; } void WiFiManager::setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { _ap_static_ip = ip; _ap_static_gw = gw; _ap_static_sn = sn; } void WiFiManager::setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { _sta_static_ip = ip; _sta_static_gw = gw; _sta_static_sn = sn; } void WiFiManager::setMinimumSignalQuality(int quality) { _minimumQuality = quality; } void WiFiManager::setBreakAfterConfig(boolean shouldBreak) { _shouldBreakAfterConfig = shouldBreak; } /** * HTTPD CALLBACK root or redirect to captive portal */ void WiFiManager::handleRoot() { DEBUG_WM(F("Handle root")); if (captivePortal()) { // If captive portal redirect instead of displaying the page. return; } String page = FPSTR(HTTP_HEAD); page.replace("{v}", "Options"); page += FPSTR(HTTP_SCRIPT); page += FPSTR(HTTP_STYLE); page += _customHeadElement; page += FPSTR(HTTP_HEAD_END); page += "