From 31568d3f53f23371dcc5d0da199262aec329d906 Mon Sep 17 00:00:00 2001 From: gilex-dev Date: Mon, 21 Jul 2025 23:04:01 +0200 Subject: [PATCH] Add WiFi configuration loader --- data/config/WiFiManager.json | 52 ++++++ lib/WiFiManager/src/WiFiManager.cpp | 252 ++++++++++++++++++++++++++++ lib/WiFiManager/src/WiFiManager.h | 43 +++++ platformio.ini | 19 +++ src/logger/logger.cpp | 62 +++++++ src/logger/logger.h | 5 + src/main.cpp | 58 +++++-- src/main.h | 0 8 files changed, 481 insertions(+), 10 deletions(-) create mode 100644 data/config/WiFiManager.json create mode 100644 lib/WiFiManager/src/WiFiManager.cpp create mode 100644 lib/WiFiManager/src/WiFiManager.h create mode 100644 src/logger/logger.cpp create mode 100644 src/logger/logger.h create mode 100644 src/main.h diff --git a/data/config/WiFiManager.json b/data/config/WiFiManager.json new file mode 100644 index 0000000..09dfa06 --- /dev/null +++ b/data/config/WiFiManager.json @@ -0,0 +1,52 @@ +{ + "fallbackAp": "defaultAP", + "WiFiAp": [ + { + "name": "defaultAP", + "ap": true, + "mac": "", + "ssid": "ESP Setup", + "bssid": "", + "psk": "tempPW", + "hostname": "esp-template", + "retries": 0, + "retryIntervall": 0, + "ipv4": { + "gateway": "", + "dns": "", + "dnsFallback": "", + "address": "" + }, + "ipv6": { + "gateway": "", + "dns": "", + "dnsFallback": "", + "address": "" + } + } + ], + "WiFiSta": [ + { + "name": "john doe", + "ap": "defaultAP", + "ssid": "john doe", + "bssid": "", + "psk": "", + "hostname": "", + "retries": 0, + "retryIntervall": 0, + "ipv4": { + "gateway": "", + "dns": "", + "dnsFallback": "", + "address": "" + }, + "ipv6": { + "gateway": "", + "dns": "", + "dnsFallback": "", + "address": "" + } + } + ] +} diff --git a/lib/WiFiManager/src/WiFiManager.cpp b/lib/WiFiManager/src/WiFiManager.cpp new file mode 100644 index 0000000..3147525 --- /dev/null +++ b/lib/WiFiManager/src/WiFiManager.cpp @@ -0,0 +1,252 @@ +#include "WiFiManager.h" +#include +#include +#include +#include +#include + +#ifndef DNS_PORT +#define DNS_PORT 53 +#endif + +#ifdef ESP32 +#include +#include +#include +WiFiMulti wifiMulti; +#elif defined(ESP8266) +#include +ESP8266WiFiMulti wifiMulti; +#endif + +WiFiEventHandler disconnectedEventHandler; +std::vector *connection_queue; +JsonDocument doc; +const uint8 *bssid; + +// keep track of current connection +uint currentConfig; +uint currentTry; +bool inRecovery = false; + +unsigned long tryStartMillis; +unsigned long lastCheckMillis; + +int sortByRSSI(const void *a, const void *b) { + return (((bss_info *)b)->rssi - ((bss_info *)a)->rssi); +} + +bool matches_ssid(const std::vector &networks, JsonVariant config) { + for (auto &&network : networks) { + if (memcmp((char *)network->ssid, config["ssid"].as().c_str(), network->ssid_len) == 0) { + return true; + } + } + return false; +} + +bool matches_bssid(const std::vector &networks, JsonVariant config) { + char bssid_char[sizeof(bss_info().bssid) * 3]; + for (auto &&network : networks) { + bssid = network->bssid; + snprintf_P(bssid_char, sizeof(bssid_char), "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); + + for (auto &&config_bssid : config["bssid"].as()) { + if (config_bssid == bssid_char) { + return true; + } + } + } + return false; +} + +void disconnectHandler(const WiFiEventStationModeDisconnected &event = {}) { + if (inRecovery) { + return; + } + + Log.noticeln("In disc-handler"); + JsonVariant config = connection_queue->at(currentConfig); + if (currentTry < config["retries"]) { + currentTry++; + Log.noticeln("trying again to connect"); + } else { + if (currentConfig + 1 == connection_queue->size()) { + Log.noticeln("Station disconnected. No more configurations to try. Starting recovery sequence"); + inRecovery = true; + WiFi.disconnect(); + WiFi.softAP("ESP reached recovery state", "tempPW12345"); + return; + } else { + currentTry = 0; + currentConfig++; + config = connection_queue->at(currentConfig); + } + } + + Log.noticeln("Station disconnected. Connecting to %s", config["name"].as().c_str()); + + String psk; + const uint8_t *bssid = nullptr; + + if (!config["psk"].isNull()) { + psk = config["psk"].as(); + } + + if (!config["bssid"].isNull() && config["bssid"].size() > 0) { + Log.noticeln("Found bssid %s", config["bssid"].as().c_str()); + bssid = (const uint8_t *)config["bssid"].as().c_str(); + } + + Log.noticeln("Connecting with: %s %s %s", config["ssid"].as().c_str(), psk, bssid); + tryStartMillis = millis(); + if (!config["hostname"].isNull()) { + Log.noticeln("Set hostname %s", config["hostname"].as().c_str()); + WiFi.setHostname(config["hostname"].as().c_str()); + } + + WiFi.begin(config["ssid"].as(), psk, 0); + Log.noticeln("Set Wifi to ssid: '%s' psk: '%s' bssid: %s", WiFi.SSID().c_str(), WiFi.psk().c_str(), WiFi.BSSIDstr()); +} + +void getScanResults(int count) { + Log.noticeln("Scan done. Found %i radios", count); + + char bssid_char[sizeof(bss_info().bssid) * 3]; + + std::vector networks; + networks.resize(count); + uint8 max_ssid_length = 0; + for (int i = 0; i < count; i++) { + networks.at(i) = WiFi.getScanInfoByIndex(i); + max_ssid_length = max(networks.at(i)->ssid_len, max_ssid_length); + } + + std::sort(networks.begin(), networks.end(), sortByRSSI); + + char buffer[128]; + + for (auto &network : networks) { + bssid = network->bssid; + snprintf_P(bssid_char, sizeof(bssid_char), "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); + snprintf_P(buffer, sizeof(buffer), "SSID: %-*s, RSSI: %i, BSSID: %s, hidden: %i", max_ssid_length, network->ssid, network->rssi, bssid_char, network->is_hidden); + Log.noticeln("%s", buffer); + } + + connection_queue = new std::vector; + connection_queue->reserve(doc["WiFiSta"].size()); // worst case: all configurations are valid + + // TODO: add every found bssid for a given ssid to the queue (in case one bssid with same ssid has a different psk) + for (JsonVariant config : doc["WiFiSta"].as()) { + Log.noticeln("bssid is Null: %d, BSSID is empty: %d", config["bssid"].isNull(), config["bssid"].as().length()); + Log.noticeln("if (%t or (%t and (%t or %t) or %t))", config["alwaysTry"].as(), matches_ssid(networks, config), config["bssid"].isNull(), config["bssid"].as().length() == 0, matches_bssid(networks, config)); + if (config["alwaysTry"].as() or (matches_ssid(networks, config) and ((config["bssid"].isNull() or config["bssid"].as().length() == 0) or matches_bssid(networks, config)))) { + connection_queue->push_back(config); + Log.noticeln("Adding %s to queue", config["name"].as().c_str()); + } + } + disconnectHandler(); // start connecting to configurations +} + +wl_status_t WiFiManager::tick(unsigned long update_interval = 100) { + unsigned long currentMillis = millis(); + if (!(currentMillis - lastCheckMillis >= update_interval)) { + return this->lastState; + } + + Log.noticeln("In tick"); + + lastCheckMillis = currentMillis; + + this->lastState = WiFi.status(); + + if (this->lastState == WL_CONNECTED) { + return this->lastState; + } + + if (connection_queue == nullptr || currentConfig >= connection_queue->size()) { + Log.warningln("Tried to tick but config still loading"); + return WL_IDLE_STATUS; + } + + Log.noticeln("In tick with %d configs", connection_queue->size()); + + if (currentMillis - tryStartMillis >= connection_queue->at(currentConfig)["timeout"]) { + Log.noticeln("Disconnect from WiFi (reached timeout)"); + WiFi.disconnect(); + } + return WL_DISCONNECTED; +} + +WiFiManager::WiFiManager() {} +WiFiManager::~WiFiManager() {} +void WiFiManager::begin() { + + // TODO: load connections +} +bool WiFiManager::loadFromJson(File file) { + + // Deserialize the JSON document + DeserializationError error = deserializeJson(doc, file); + if (error) + Serial.println(F("Failed to read file, using default configuration")); + + String setupAP; + + Log.noticeln("Found %i configurations, overflowed: %T ", doc["WiFiSta"].size(), doc.overflowed()); + + setupAP = doc["SetupAP"].as(); + + for (JsonVariant i : doc["WiFiSta"].as()) { + Serial.println(i["name"].as() + i["ssid"].as() + i["psk"].as()); + Log.noticeln("Name: %s", i["name"].as().c_str()); + Log.noticeln("SSID: %s", i["ssid"].as().c_str()); + Log.noticeln("PSK: %s", i["psk"].as().c_str()); + } + + disconnectedEventHandler = WiFi.onStationModeDisconnected(disconnectHandler); + + WiFi.scanNetworksAsync(getScanResults, true); + + loadConnections(); + + file.close(); + return true; +} + +bool WiFiManager::loadConnections() { + WiFi.persistent(false); + WiFi.mode(WIFI_AP_STA); + + IPAddress local_ip, gateway, subnet; + local_ip.fromString("10.0.0.1"); + gateway.fromString("10.0.0.1"); + subnet.fromString(F("255.255.255.0")); + + uint8_t newMACAddress[WL_MAC_ADDR_LENGTH] = {0x32, 0xAE, 0xA4, 0x07, 0x0D, 0x66}; + + const bool ap = true; + + if (ap) { + + if (setApMac(newMACAddress)) + Log.infoln("set MAC for SOFTAP_IF to %s", WiFi.softAPmacAddress().c_str()); + else + Log.warningln("Unable to set MAC for SOFTAP_IF. Using %s instead", WiFi.softAPmacAddress().c_str()); + WiFi.softAPConfig(local_ip, gateway, subnet); + WiFi.softAP("ESP Setup", "tempPW123", 5, 0, 1); + Serial.println(WiFi.softAPIP()); + Serial.println(WiFi.macAddress()); + } + + return true; +} + +bool WiFiManager::setApMac(uint8_t mac[WL_MAC_ADDR_LENGTH]) { + wifi_set_macaddr(SOFTAP_IF, &mac[0]); + + char charMacAddress[(sizeof(*mac) * 3)]; + snprintf_P(charMacAddress, sizeof(charMacAddress), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + String actualMac = WiFi.softAPmacAddress(); + return strncmp(charMacAddress, actualMac.c_str(), sizeof(charMacAddress)); +} diff --git a/lib/WiFiManager/src/WiFiManager.h b/lib/WiFiManager/src/WiFiManager.h new file mode 100644 index 0000000..c3a7c6b --- /dev/null +++ b/lib/WiFiManager/src/WiFiManager.h @@ -0,0 +1,43 @@ +#ifdef ESP32 +#include +#include +#include +#elif defined(ESP8266) +#include +#endif + +#ifndef WL_IPV6_LENGTH +#define WL_IPV6_LENGTH 16 +#endif + +struct WiFiConnection { + char *ssid; + char *bssid; + char *psk; + bool hidden; + char *hostname; + byte mac[WL_MAC_ADDR_LENGTH]; + byte ipv4[WL_IPV4_LENGTH]; + byte ipv6[WL_IPV6_LENGTH]; +}; + +class WiFiManager { + private: + unsigned int reboot_count; + WiFiConnection *allConnections; + bool loadConfig(File file); + bool loadConnections(); + void handleUnsuccessfulConnect(); + bool connect(); + void disconnect(); + bool setApMac(uint8_t mac[WL_MAC_ADDR_LENGTH]); + // void getScanResults(int count); + + public: + wl_status_t lastState; + WiFiManager(/* args */); + ~WiFiManager(); + void begin(); + wl_status_t tick(unsigned long update_interval); + bool loadFromJson(File file); +}; diff --git a/platformio.ini b/platformio.ini index 6bb127a..31b146b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -8,7 +8,26 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html +[platformio] +default_envs = nodemcuv2 + +[common_env_data] +build_flags = '-D BUILD_VERSION="0.0.1"' -D DEBUG_ESP_PORT=Serial -D NDEBUG +extra_scripts = + ./extra_scripts/reset_board.py + [env:nodemcuv2] platform = espressif8266 board = nodemcuv2 framework = arduino +upload_speed = 921600 +monitor_speed = 74880 +monitor_filters = esp8266_exception_decoder +board_build.filesystem = littlefs +build_flags = ${common_env_data.build_flags} -D SERIAL_BAUD_RATE=${this.monitor_speed} -D BUILD_DEBUG=false +extra_scripts = ${common_env_data.extra_scripts} +lib_compat_mode = strict +lib_ldf_mode = chain +lib_deps = + thijse/ArduinoLog @ ^1.1.1 + bblanchon/ArduinoJson @ ^7.4.2 diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp new file mode 100644 index 0000000..df4cb00 --- /dev/null +++ b/src/logger/logger.cpp @@ -0,0 +1,62 @@ +#include + +void printTimestamp(Print *_logOutput) { + + // Division constants + const unsigned long MSECS_PER_SEC = 1000; + const unsigned long SECS_PER_MIN = 60; + const unsigned long SECS_PER_HOUR = 3600; + const unsigned long SECS_PER_DAY = 86400; + + // Total time + const unsigned long msecs = millis(); + const unsigned long secs = msecs / MSECS_PER_SEC; + + // Time in components + const unsigned long MilliSeconds = msecs % MSECS_PER_SEC; + const unsigned long Seconds = secs % SECS_PER_MIN; + const unsigned long Minutes = (secs / SECS_PER_MIN) % SECS_PER_MIN; + const unsigned long Hours = (secs % SECS_PER_DAY) / SECS_PER_HOUR; + + // Time as string + char timestamp[20]; + sprintf(timestamp, "%02lu:%02lu:%02lu.%03lu ", Hours, Minutes, Seconds, MilliSeconds); + _logOutput->print(timestamp); +} + +void printLogLevel(Print *_logOutput, int logLevel) { + /// Show log description based on log level + switch (logLevel) { + default: + case 0: + _logOutput->print("SILENT "); + break; + case 1: + _logOutput->print("FATAL "); + break; + case 2: + _logOutput->print("ERROR "); + break; + case 3: + _logOutput->print("WARNING "); + break; + case 4: + _logOutput->print("INFO "); + break; + case 5: + _logOutput->print("TRACE "); + break; + case 6: + _logOutput->print("VERBOSE "); + break; + } +} + +void printSuffix(Print *_logOutput, int logLevel) { + _logOutput->print(""); +} + +void printPrefix(Print *_logOutput, int logLevel) { + printTimestamp(_logOutput); + printLogLevel(_logOutput, logLevel); +} diff --git a/src/logger/logger.h b/src/logger/logger.h new file mode 100644 index 0000000..48c8e8e --- /dev/null +++ b/src/logger/logger.h @@ -0,0 +1,5 @@ +#include + +void printPrefix(Print *_logOutput, int logLevel); + +void printSuffix(Print *_logOutput, int logLevel); diff --git a/src/main.cpp b/src/main.cpp index cb9fbba..57b1b4a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,18 +1,56 @@ +#include "main.h" +#include "logger/logger.h" #include +#include +#include +#include -// put function declarations here: -int myFunction(int, int); +#ifndef SERIAL_BAUD_RATE +#define SERIAL_BAUD_RATE 115200 +#endif + +uint8_t mac[WL_MAC_ADDR_LENGTH]; + +WiFiManager manager; + +unsigned long currentMillis, oldMillis; void setup() { - // put your setup code here, to run once: - int result = myFunction(2, 3); + Serial.begin(SERIAL_BAUD_RATE); + Log.setPrefix(printPrefix); + Log.setSuffix(printSuffix); + Log.begin(LOG_LEVEL_VERBOSE, &Serial); + delay(2000); + + Serial.println("Starting up"); + +#ifdef ESP32 + LittleFS.begin(true); +#else + LittleFS.begin(); +#endif + + File file = LittleFS.open("/config/WiFiManager.json", "r"); + if (!file) { + Serial.println("Failed to open file for reading"); + } else { + + manager.loadFromJson(file); + + file.close(); + } } void loop() { - // put your main code here, to run repeatedly: -} -// put function definitions here: -int myFunction(int x, int y) { - return x + y; -} \ No newline at end of file + currentMillis = millis(); + + if (currentMillis - oldMillis >= 1000) { + oldMillis = currentMillis; + } + + if (manager.tick(1000) == WL_CONNECTED) { + // regularOperationStuff(); + } + // importantStuff(); +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..e69de29