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

"; 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(); }; // 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"; } // Set default SSID if none provided _ssid = ssid.isEmpty() ? "SmartCube_" + String(ESP.getChipId()) : 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(); display.clearDisplay(); display.setCursor(0, 0); display.println("Connecting to wifi"); display.display(); 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 bool netman::tryConnectToSsid(const char* ssid, const char* pass) { WiFi.begin(ssid, pass); unsigned long start = millis(); display.println("Connecting to " + String(ssid) + "..."); while (millis() - start < connectionTimeout) { delay(500); if (WiFi.status() == WL_CONNECTED) { display.println("Connected!"); return true; } } display.println("Connection failed."); 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.println("SSID:"); display.println(_ssid.c_str()); display.println("IP:"); 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->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; } // Add SSID to config and save void netman::addSsid(String ssid, String password) { _ssids[ssid] = password; writeConfig(); } // 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(); } // Web handlers for adding/removing SSIDs void netman::handleRoot() { if (redirectToIp()) return; String result = beginHtml; for (const auto& item : _ssids) { result += "" + item.first + "-" + item.second + ""; } 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(); }