/* MIT License Copyright (c) 2021-22 Magnus 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. */ #if defined(ESP8266) #include #include #else // defined (ESP32) #include #include #include #include #endif #include #include #include // Settings for DRD #define ESP_DRD_USE_LITTLEFS true #define ESP_DRD_USE_SPIFFS false #define ESP_DRD_USE_EEPROM false #include #define DRD_TIMEOUT 3 #define DRD_ADDRESS 0 // Settings for WIFI Manager #define USE_ESP_WIFIMANAGER_NTP false #define USE_CLOUDFLARE_NTP false #define USING_CORS_FEATURE false #define NUM_WIFI_CREDENTIALS 1 #define USE_STATIC_IP_CONFIG_IN_CP false #define _WIFIMGR_LOGLEVEL_ 3 #include ESP_WiFiManager *myWifiManager; DoubleResetDetector *myDRD; WifiConnection myWifi; const char *userSSID = USER_SSID; const char *userPWD = USER_SSID_PWD; void WifiConnection::init() { myDRD = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS); } bool WifiConnection::hasConfig() { if (strlen(myConfig.getWifiSSID(0))) return true; if (strlen(userSSID)) return true; // Check if there are stored WIFI Settings we can use. #if defined(ESP8266) String ssid = WiFi.SSID(); String pwd = WiFi.psk(); #else ESP_WiFiManager wifiMgr; String ssid = wifiMgr.WiFi_SSID(); String pwd = wifiMgr.WiFi_Pass(); #endif if (ssid.length()) { Log.notice(F("WIFI: Found stored credentials." CR)); myConfig.setWifiSSID(ssid, 0); if (pwd.length()) myConfig.setWifiPass(pwd, 0); myConfig.saveFile(); return true; } return false; } bool WifiConnection::isConnected() { return WiFi.status() == WL_CONNECTED; } String WifiConnection::getIPAddress() { return WiFi.localIP().toString(); } bool WifiConnection::isDoubleResetDetected() { if (strlen(userSSID)) return false; // Ignore this if we have hardcoded settings. return myDRD->detectDoubleReset(); } void WifiConnection::stopDoubleReset() { myDRD->stop(); } void WifiConnection::startPortal() { Log.notice(F("WIFI: Starting Wifi config portal." CR)); pinMode(PIN_LED, OUTPUT); digitalWrite(PIN_LED, LOW); myWifiManager = new ESP_WiFiManager(WIFI_MDNS); myWifiManager->setMinimumSignalQuality(-1); myWifiManager->setConfigPortalChannel(0); myWifiManager->setConfigPortalTimeout( myAdvancedConfig.getWifiPortalTimeout()); String mdns("

Default mDNS name is: http://"); mdns += myConfig.getMDNS(); mdns += ".local

"; ESP_WMParameter deviceName(mdns.c_str()); myWifiManager->addParameter(&deviceName); #if defined(ESP32C3) Log.notice(F("WIFI: Reducing wifi power for c3 chip." CR)); WiFi.setTxPower(WIFI_POWER_8_5dBm); // Required for ESP32C3 Mini #endif myWifiManager->startConfigPortal(WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD); if (myWifiManager->getSSID(0).length()) { myConfig.setWifiSSID(myWifiManager->getSSID(0), 0); myConfig.setWifiPass(myWifiManager->getPW(0), 0); myConfig.setWifiSSID(myWifiManager->getSSID(1), 1); myConfig.setWifiPass(myWifiManager->getPW(1), 1); // If the same SSID has been used, lets delete the second if (!strcmp(myConfig.getWifiSSID(0), myConfig.getWifiSSID(1))) { myConfig.setWifiSSID("", 1); myConfig.setWifiPass("", 1); } Log.notice(F("WIFI: Stored SSID1:'%s' SSID2:'%s'" CR), myConfig.getWifiSSID(0), myConfig.getWifiSSID(1)); myConfig.saveFile(); } else { Log.notice( F("WIFI: Could not find first SSID so assuming we got a timeout." CR)); } Log.notice(F("WIFI: Exited wifi config portal. Rebooting..." CR)); stopDoubleReset(); delay(500); ESP_RESET(); } void WifiConnection::loop() { myDRD->loop(); } void WifiConnection::connectAsync(int wifiIndex) { WiFi.persistent(true); WiFi.mode(WIFI_STA); #if defined(ESP32C3) Log.notice(F("WIFI: Reducing wifi power for c3 chip." CR)); WiFi.setTxPower(WIFI_POWER_8_5dBm); // Required for ESP32C3 Mini #endif if (strlen(userSSID)) { Log.notice(F("WIFI: Connecting to wifi using hardcoded settings %s." CR), userSSID); WiFi.begin(userSSID, userPWD); } else { Log.notice(F("WIFI: Connecting to wifi (%d) using stored settings %s." CR), wifiIndex, myConfig.getWifiSSID(wifiIndex)); WiFi.begin(myConfig.getWifiSSID(wifiIndex), myConfig.getWifiPass(wifiIndex)); } } bool WifiConnection::waitForConnection(int maxTime) { #if DEBUG_LEVEL == 6 WiFi.printDiag(EspSerial); #endif int i = 0; while (WiFi.status() != WL_CONNECTED) { delay(100); if (i % 10) EspSerial.print("."); if (i++ > (maxTime * 10)) { // Try for maxTime seconds. Since delay is 100ms. writeErrorLog("WIFI: Failed to connect to wifi %d", WiFi.status()); WiFi.disconnect(); EspSerial.print(CR); return false; // Return to main that we have failed to connect. } } EspSerial.print(CR); Log.notice(F("WIFI: Connected to wifi %s ip=%s." CR), WiFi.SSID().c_str(), getIPAddress().c_str()); Log.notice(F("WIFI: Using mDNS name %s." CR), myConfig.getMDNS()); return true; } int WifiConnection::findStrongestNetwork() { if (!myConfig.dualWifiConfigured()) { Log.notice(F("WIFI: Only one wifi SSID is configured, skipping scan." CR)); return -1; } Log.notice(F("WIFI: Scanning for wifi." CR)); int noNetwork = WiFi.scanNetworks(false, false); int strenght[2] = {-100, -100}; for (int i = 0; i < noNetwork; i++) { int rssi = WiFi.RSSI(i); String ssid = WiFi.SSID(i); if (ssid.compareTo(myConfig.getWifiSSID(0))) strenght[0] = rssi; else if (ssid.compareTo(myConfig.getWifiSSID(1))) strenght[1] = rssi; #if LOG_LEVEL == 6 Log.verbose(F("WIFI: Found %s %d." CR), ssid.c_str(), rssi); #endif } if (strenght[0] == -100 && strenght[1] == -100) return -1; // None of the stored networks can be seen if (strenght[0] >= strenght[1]) return 0; // First network is strongest or they have equal strength return 1; // Second network is the strongest } bool WifiConnection::connect() { /* // Alternative code for connecting to strongest wifi. // Takes approximatly 2 seconds to scan for available network int i = findStrongestNetwork(); if (i != -1) { Log.notice(F("WIFI: Wifi %d:'%s' is strongest." CR), i, myConfig.getWifiSSID(i)); } else { i = 0; // Use first SSID as default. } connectAsync(i); return waitForConnection(myAdvancedConfig.getWifiConnectTimeout()); */ // Alternative code for using seconday wifi settings as fallback. // If success to seconday is successful this is used as standard int timeout = myAdvancedConfig.getWifiConnectTimeout(); connectAsync(0); if (!waitForConnection(timeout)) { Log.warning(F("WIFI: Failed to connect to first SSID %s." CR), myConfig.getWifiSSID(0)); if (strlen(myConfig.getWifiSSID(1))) { connectAsync(1); if (waitForConnection(timeout)) { Log.notice(F("WIFI: Connected to second SSID %s, making secondary " "default." CR), myConfig.getWifiSSID(1)); myConfig.swapPrimaryWifi(); myConfig.saveFile(); return true; } } Log.warning(F("WIFI: Failed to connect to any SSID." CR)); return false; } return true; } bool WifiConnection::disconnect() { Log.notice(F("WIFI: Erasing stored WIFI credentials." CR)); // Erase WIFI credentials return WiFi.disconnect(true); } #if defined(ACTIVATE_OTA) bool WifiConnection::updateFirmware() { if (!_newFirmware) { Log.notice(F("WIFI: No newer version exist, skipping update." CR)); return false; } #if LOG_LEVEL == 6 Log.verbose(F("WIFI: Updating firmware." CR)); #endif WiFiClient wifi; WiFiClientSecure wifiSecure; HTTPUpdateResult ret; String serverPath = myConfig.getOtaURL(); #if defined(ESP8266) serverPath += "firmware.bin"; #elif defined(ESP32C3) serverPath += "firmware32c3.bin"; #elif defined(ESP32S2) serverPath += "firmware32s2.bin"; #else // defined (ESP32) serverPath += "firmware32.bin"; #endif if (serverPath.startsWith("https://")) { wifiSecure.setInsecure(); Log.notice(F("WIFI: OTA, SSL enabled without validation." CR)); ret = ESPhttpUpdate.update(wifiSecure, serverPath); } else { ret = ESPhttpUpdate.update(wifi, serverPath); } switch (ret) { case HTTP_UPDATE_FAILED: { writeErrorLog("WIFI: OTA update failed %d", ESPhttpUpdate.getLastError()); } break; case HTTP_UPDATE_NO_UPDATES: break; case HTTP_UPDATE_OK: { Log.notice("WIFI: OTA Update sucesfull, rebooting."); delay(100); ESP_RESET(); break; } } return false; } void WifiConnection::downloadFile(HTTPClient &http, String &fname) { #if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING) Log.verbose(F("WIFI: Download file %s." CR), fname); #endif int httpResponseCode = http.GET(); if (httpResponseCode == 200) { File f = LittleFS.open(fname, "w"); http.writeToStream(&f); f.close(); Log.notice(F("WIFI: Downloaded file %s." CR), fname.c_str()); } else { writeErrorLog("WIFI: Failed to download html-file %d", httpResponseCode); } } bool WifiConnection::checkFirmwareVersion() { #if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING) Log.verbose(F("WIFI: Checking if new version exist." CR)); #endif WiFiClient wifi; WiFiClientSecure wifiSecure; HTTPClient http; String serverPath = myConfig.getOtaURL(); serverPath += "version.json"; // Your Domain name with URL path or IP address with path if (myConfig.isOtaSSL()) { wifiSecure.setInsecure(); Log.notice(F("WIFI: OTA, SSL enabled without validation." CR)); http.begin(wifiSecure, serverPath); } else { http.begin(wifi, serverPath); } // Send HTTP GET request DynamicJsonDocument ver(300); int httpResponseCode = http.GET(); if (httpResponseCode == 200) { Log.notice(F("WIFI: Found version.json, response=%d" CR), httpResponseCode); String payload = http.getString(); #if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING) Log.verbose(F("WIFI: Payload %s." CR), payload.c_str()); #endif DeserializationError err = deserializeJson(ver, payload); if (err) { writeErrorLog("WIFI: Failed to parse version.json"); } else { #if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING) Log.verbose(F("WIFI: Project %s version %s." CR), (const char *)ver["project"], (const char *)ver["version"]); #endif int newVer[3]; int curVer[3]; if (parseFirmwareVersionString(newVer, (const char *)ver["version"])) { if (parseFirmwareVersionString(curVer, CFG_APPVER)) { #if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING) Log.verbose(F("WIFI: OTA checking new=%d.%d.%d cur=%d.%d.%d" CR), newVer[0], newVer[1], newVer[2], curVer[0], curVer[1], curVer[2]); #endif // Compare major version if (newVer[0] > curVer[0]) _newFirmware = true; // Compare minor version else if (newVer[0] == curVer[0] && newVer[1] > curVer[1]) _newFirmware = true; // Compare patch version else if (newVer[0] == curVer[0] && newVer[1] == curVer[1] && newVer[2] > curVer[2]) _newFirmware = true; } } // Download new html files to filesystem if they are present. if (!ver["html"].isNull() && _newFirmware) { Log.notice( F("WIFI: OTA checking if html files should be downloaded." CR)); JsonArray htmlFiles = ver["html"].as(); for (JsonVariant v : htmlFiles) { String s = v; #if LOG_LEVEL == 6 Log.verbose(F("WIFI: OTA listed html file %s" CR), s.c_str()); #endif downloadFile(http, s); } } } } else { Log.error(F("WIFI: OTA error checking version.json, response=%d" CR), httpResponseCode); } http.end(); #if LOG_LEVEL == 6 Log.verbose(F("WIFI: OTA found new version %s." CR), _newFirmware ? "true" : "false"); #endif return _newFirmware; } bool WifiConnection::parseFirmwareVersionString(int (&num)[3], const char *version) { #if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING) Log.verbose(F("WIFI: Parsing version number string %s." CR), version); #endif char temp[80]; char *s; char *p = &temp[0]; int i = 0; // strcpy(&temp[0], version); snprintf(&temp[0], sizeof(temp), "%s", version); while ((s = strtok_r(p, ".", &p)) != NULL) { num[i++] = atoi(s); } return true; } #endif // ACTIVATE_OTA // EOF