#include #include #include #include #include #include #include // Constants for the HTML pages and config file static const String beginHtml = "SmartCube Configure

Configure AP


"; static const String endHtml = "
"; static const String configFile = "/netman"; // Timeout for WiFi connection attempts static const unsigned long connectionTimeout = 15000; // 15 seconds struct netman { public: netman(Adafruit_SSD1306& display); netman(String ssid, String pass, bool hidden, Adafruit_SSD1306& display); bool start(); void reset(); void addSsid(String ssid, String password); void removeSsid(String ssid, String password); private: Adafruit_SSD1306& display; std::unique_ptr server; std::map _ssids; String _ssid, _pass; bool _hidden; void init(String ssid, String pass, bool hidden); bool tryConnectToSsid(const char* ssid, const char* pass); bool tryConnect(); void createAP(); bool redirectToIp(); void readConfig(); void writeConfig(); void handleRoot(); void handleAdd(); void handleRemove(); void handleSelect(); }; // Initialization void netman::init(String ssid, String pass, bool hidden) { // Ensure password meets minimum length if (pass != "" && pass.length() < 8) { display.clearDisplay(); display.setCursor(0, 0); display.println("Password too short"); display.display(); pass = "8characters"; } // Get the last 4 characters of the MAC address String macAddress = WiFi.macAddress(); String macSuffix = macAddress.substring(macAddress.length() - 5); macSuffix.replace(":", ""); // Set default SSID if none provided _ssid = ssid.isEmpty() ? "SmartCube_" + macSuffix : ssid; _pass = pass; _hidden = hidden; // Initialize LittleFS with error handling if (!LittleFS.begin()) { display.clearDisplay(); display.setCursor(0, 0); display.println("FS init failed"); display.display(); } } // Constructors netman::netman(Adafruit_SSD1306& display) : display(display) { init("", "", false); } netman::netman(String ssid, String pass, bool hidden, Adafruit_SSD1306& display) : display(display) { init(ssid, pass, hidden); } // Attempt to start and connect or create AP bool netman::start() { if (_pass == "8characters") { display.clearDisplay(); display.setCursor(0, 0); display.println("Using default pass:"); display.println("8characters"); display.display(); } return tryConnect() || (createAP(), false); } // Attempt connection to each saved SSID in order bool netman::tryConnect() { readConfig(); for (auto const& item : _ssids) { if (tryConnectToSsid(item.first.c_str(), item.second.c_str())) { return true; } } return false; } // Attempt to connect to a specific SSID with timeout, dot animation, and IP display bool netman::tryConnectToSsid(const char* ssid, const char* pass) { WiFi.begin(ssid, pass); unsigned long start = millis(); // Clear display and set initial message display.clearDisplay(); display.setCursor(0, 0); display.println("Connecting to WiFi:"); display.setCursor(0, 11); display.println(String(ssid)); display.display(); int dotCount = 0; while (millis() - start < connectionTimeout) { delay(500); // Check WiFi connection status if (WiFi.status() == WL_CONNECTED) { // Success message with IP address display.clearDisplay(); display.setCursor(0, 0); display.println("Connected!"); display.setCursor(0, 12); display.print("IP: "); display.println(WiFi.localIP()); display.display(); delay(500); return true; } // Animate by adding dots display.setCursor(0, 20); for (int i = 0; i < dotCount; i++) { display.print("."); } display.display(); dotCount = (dotCount + 1); } // Connection failed display.clearDisplay(); display.setCursor(0, 0); display.println("Connection failed."); display.display(); WiFi.disconnect(); return false; } // Setup Access Point with DNS and HTTP server void netman::createAP() { WiFi.softAPdisconnect(true); WiFi.mode(WIFI_AP); WiFi.softAP(_ssid.c_str(), _pass.c_str(), 1, _hidden); display.clearDisplay(); display.setCursor(0, 0); display.println("AccessPoint created"); display.setCursor(0, 14); display.println("SSID:"); display.setCursor(0, 24); display.setTextSize(1); display.println(_ssid.c_str()); display.setTextSize(1); display.setCursor(0, 40); display.println("Config portal:"); display.setCursor(0, 50); display.print("http://"); display.println(WiFi.softAPIP().toString()); display.display(); server.reset(new ESP8266WebServer(80)); DNSServer dnsServer; dnsServer.start(53, "*", WiFi.softAPIP()); server->on("/", std::bind(&netman::handleRoot, this)); server->on("/add", std::bind(&netman::handleAdd, this)); server->on("/remove", std::bind(&netman::handleRemove, this)); server->on("/select", std::bind(&netman::handleSelect, this)); server->begin(); while (true) { dnsServer.processNextRequest(); server->handleClient(); delay(10); } } // Redirect to AP IP if not accessed directly bool netman::redirectToIp() { if (server->hostHeader() == WiFi.softAPIP().toString()) { return false; } server->sendHeader("Location", "http://" + WiFi.softAPIP().toString(), true); server->send(302, "text/plain", ""); server->client().stop(); return true; } // Modify the addSsid function to take parameters from the `select` page void netman::addSsid(String ssid, String password) { _ssids[ssid] = password; writeConfig(); // Attempt to connect to the selected network if (tryConnectToSsid(ssid.c_str(), password.c_str())) { // Redirect to the main page on success server->sendHeader("Location", "/", true); server->send(302, "text/plain", ""); } else { // Show error message if connection failed server->send(200, "text/html", "

Connection failed. Please try again.

"); } } // Remove SSID from config void netman::removeSsid(String ssid, String password) { if (_ssids.count(ssid) && _ssids[ssid] == password) { _ssids.erase(ssid); writeConfig(); } } // Handle file-based config loading void netman::readConfig() { _ssids.clear(); File file = LittleFS.open(configFile, "r"); if (!file) { display.clearDisplay(); display.setCursor(0, 0); display.println("Config not found"); display.display(); return; } while (file.available()) { String ssid = file.readStringUntil('\n'); ssid.trim(); String pass = file.readStringUntil('\n'); pass.trim(); _ssids[ssid] = pass; } file.close(); } // Handle file-based config saving void netman::writeConfig() { File file = LittleFS.open(configFile, "w"); for (const auto& item : _ssids) { file.println(item.first); file.println(item.second); } file.close(); } // Reset configuration void netman::reset() { LittleFS.remove(configFile); _ssids.clear(); } String urlEncode(const String &str) { String encoded = ""; char c; for (size_t i = 0; i < str.length(); i++) { c = str.charAt(i); if (isalnum(c)) { encoded += c; } else { encoded += '%'; char buf[3]; snprintf(buf, sizeof(buf), "%02X", c); encoded += buf; } } return encoded; } void netman::handleRoot() { if (redirectToIp()) return; // Scan for available networks int n = WiFi.scanNetworks(); String result = beginHtml; // Add stored SSIDs to the page result += "

Saved Networks

"; for (const auto& item : _ssids) { result += "" + item.first + "-" + item.second + ""; } // Display available WiFi networks result += "

Available Networks

"; for (int i = 0; i < n; i++) { // Get SSID and signal strength String ssid = WiFi.SSID(i); int rssi = WiFi.RSSI(i); bool openNetwork = (WiFi.encryptionType(i) == ENC_TYPE_NONE); // Show network with button to select result += ""; } result += endHtml; server->send(200, "text/html", result); } void netman::handleAdd() { server->send(200, "text/html", "The ESP will now reboot."); addSsid(server->arg("ssid"), server->arg("pass")); delay(500); ESP.restart(); } void netman::handleRemove() { removeSsid(server->arg("ssid"), server->arg("pass")); handleRoot(); } // Add SSID to config and save void netman::handleSelect() { String ssid = server->arg("ssid"); // Display prompt for password if network is secured String selectPage = "Connect to " + ssid + ""; selectPage += "

Connect to " + ssid + "

"; selectPage += ""; selectPage += ""; selectPage += "

"; selectPage += ""; selectPage += ""; server->send(200, "text/html", selectPage); }
" + (openNetwork ? "(Open)" : "(Secured)") + "" + String(rssi) + " dBm