From dc8f83c02c6c30673cd7f366aac2c4f6026ad90a Mon Sep 17 00:00:00 2001 From: le0m Date: Tue, 2 Jun 2026 16:28:08 +0200 Subject: [PATCH 01/11] fix: some undefined behavior and wrong frees in logs --- src/modules/log.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/modules/log.cpp b/src/modules/log.cpp index df4c402..8ca72b3 100644 --- a/src/modules/log.cpp +++ b/src/modules/log.cpp @@ -13,10 +13,10 @@ Log::Log(log_level l) : level(l) {}; Log::~Log() { if (websocket != nullptr) { websocket->closeAll(); - free(websocket); + delete websocket; } if (handler != nullptr) { - free(handler); + delete handler; } }; @@ -93,12 +93,14 @@ void Log::broadcast(std::string message) { }; std::string Log::format(const char *fmt, va_list args) { - std::vector msg; - int len = vsnprintf(&msg[0], 0, fmt, args); - msg.resize(len + 1); - vsnprintf(&msg[0], len, fmt, args); - - return std::string(&msg[0]); + va_list copy; + va_copy(copy, args); + int len = vsnprintf(nullptr, 0, fmt, copy); + va_end(copy); + std::vector msg(len + 1); + vsnprintf(msg.data(), len + 1, fmt, args); + + return std::string(msg.data()); }; void Log::error(const char *fmt, ...) { From c11971eef7e9f9d214849e0f0c5bafa4caf51f5f Mon Sep 17 00:00:00 2001 From: le0m Date: Tue, 2 Jun 2026 18:00:57 +0200 Subject: [PATCH 02/11] fix: config merge and task stack size --- src/modules/config.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/modules/config.cpp b/src/modules/config.cpp index fe2214d..568b115 100644 --- a/src/modules/config.cpp +++ b/src/modules/config.cpp @@ -37,14 +37,8 @@ void Config::asyncUpdateConfig(void *p) { }; void Config::merge(JsonVariant dst, JsonVariantConst src) { - if (!src.is()) { - dst.set(src); - - return; - } - for (JsonPairConst kvp : src.as()) { - if (dst[kvp.key()]) { + if (kvp.value().is()) { Config::merge(dst[kvp.key()], kvp.value()); } else { dst[kvp.key()] = kvp.value(); @@ -70,7 +64,7 @@ void Config::initServer(AsyncWebServer *server) { Log::instance()->info("Config JSON received\n"); request->send(204); newCfg.set(json); - xTaskCreate(asyncUpdateConfig, "Update config", 8192, this, 0, NULL); + xTaskCreate(asyncUpdateConfig, "Update config", 16384, this, 0, NULL); }); server->addHandler(handler); }; From 805b3b3057785f9a19b3462554866f0ecabbd20f Mon Sep 17 00:00:00 2001 From: le0m Date: Tue, 2 Jun 2026 18:44:36 +0200 Subject: [PATCH 03/11] fix: IP buffer too short --- src/modules/wifi.cpp | 6 ++---- src/modules/wifi.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/modules/wifi.cpp b/src/modules/wifi.cpp index 1509952..8a63609 100644 --- a/src/modules/wifi.cpp +++ b/src/modules/wifi.cpp @@ -78,7 +78,7 @@ const char* WiFiController::getIp() { ipBuffer[0] = '\0'; if (WiFi.getMode() & WIFI_MODE_AP) { - strncpy(ipBuffer, WiFi.softAPIP().toString().c_str(), MAX_IP_LENGTH); + snprintf(ipBuffer, sizeof(ipBuffer), "%s", WiFi.softAPIP().toString().c_str()); } else if (WiFi.getMode() & WIFI_MODE_STA) { if (WiFi.status() != WL_CONNECTED) { Log::instance()->debug("Unable to get IP, WiFi not connected\n"); @@ -86,13 +86,11 @@ const char* WiFiController::getIp() { return ipBuffer; } - strncpy(ipBuffer, WiFi.localIP().toString().c_str(), MAX_IP_LENGTH); + snprintf(ipBuffer, sizeof(ipBuffer), "%s", WiFi.localIP().toString().c_str()); } else { Log::instance()->warning("No IP availble because not connected to a nerwork\n"); } - ipBuffer[MAX_IP_LENGTH - 1] = '\0'; - return ipBuffer; }; diff --git a/src/modules/wifi.h b/src/modules/wifi.h index 98c03c2..e6997a8 100644 --- a/src/modules/wifi.h +++ b/src/modules/wifi.h @@ -13,7 +13,7 @@ class WiFiController { private: char urlBuffer[MAX_URL_LENGTH]; - char ipBuffer[MAX_IP_LENGTH]; + char ipBuffer[MAX_IP_LENGTH + 1]; public: WiFiController(); From 6b6918deeb90e23e2e9da1553a79a8d81bc5e6e3 Mon Sep 17 00:00:00 2001 From: le0m Date: Tue, 2 Jun 2026 19:25:50 +0200 Subject: [PATCH 04/11] chore: better string handling in OTA module --- src/modules/ota.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/modules/ota.cpp b/src/modules/ota.cpp index 121731c..300b5e1 100644 --- a/src/modules/ota.cpp +++ b/src/modules/ota.cpp @@ -16,22 +16,22 @@ void OTA::initServer(AsyncWebServer* server) { } uint8_t mode = U_FLASH; - const char* modeParam = request->getParam("mode")->value().c_str(); - if (strcasecmp(modeParam, "filesystem") == 0) { + const String& modeParam = request->getParam("mode")->value(); + if (modeParam.equalsIgnoreCase("filesystem")) { mode = U_SPIFFS; - } else if (strcasecmp(modeParam, "firmware") == 0) { + } else if (modeParam.equalsIgnoreCase("firmware")) { mode = U_FLASH; } else { - Log::instance()->error("Unknown update mode %s\n", modeParam); + Log::instance()->error("Unknown update mode %s\n", modeParam.c_str()); request->send(400, "text/plain", "unknown mode"); return; } if (request->hasParam("md5")) { - const char* hash = request->getParam("md5")->value().c_str(); - Log::instance()->info("Setting MD5 hash: %s\n", hash); - if (!Update.setMD5(hash)) { + const String& hash = request->getParam("md5")->value(); + Log::instance()->info("Setting MD5 hash: %s\n", hash.c_str()); + if (!Update.setMD5(hash.c_str())) { Log::instance()->error("Error setting MD5 update hash\n"); request->send(500, "text/plain", "error setting MD5 hash"); @@ -47,9 +47,8 @@ void OTA::initServer(AsyncWebServer* server) { } - size_t size = UPDATE_SIZE_UNKNOWN; - const char* sizeParam = request->getParam("size")->value().c_str(); - sscanf(sizeParam, "%zu", &size); // string to size_t + const String& sizeParam = request->getParam("size")->value(); + size_t size = (size_t)sizeParam.toInt(); if (!Update.begin(size, mode) || Update.hasError()) { Log::instance()->error("Error starting update: %s\n", Update.errorString()); request->send(500, "text/plain", "error starting update"); From 300ec2e4dd0dd6ffa410e73f72ecda48db2d4e01 Mon Sep 17 00:00:00 2001 From: le0m Date: Tue, 2 Jun 2026 20:20:43 +0200 Subject: [PATCH 05/11] chore: remove static --- src/filesystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 644ba12..d4bd5c0 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -34,7 +34,7 @@ void Filesystem::tree(const char *path, uint8_t depth) { // see readme "Note on PlatformIO" bool Filesystem::pathExists(const char *path) { - static struct stat pathStat; + struct stat pathStat; char fullPath[64]; snprintf(fullPath, sizeof(fullPath), "/littlefs%s", path); From 611f48a131fa6ba6d667acb25b615c882696cdcb Mon Sep 17 00:00:00 2001 From: le0m Date: Tue, 2 Jun 2026 22:23:46 +0200 Subject: [PATCH 06/11] chore: initialize pointer and add content type check in media upload --- src/modes/media.cpp | 5 +++++ src/modes/media.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/modes/media.cpp b/src/modes/media.cpp index 4531a9e..eedef07 100644 --- a/src/modes/media.cpp +++ b/src/modes/media.cpp @@ -145,7 +145,12 @@ void Media::initServer(AsyncWebServer *server) { if (index == 0) { Log::instance()->debug("Receiving file: %d bytes\n", total); const AsyncWebHeader *contentType = request->getHeader("Content-Type"); + if (contentType == nullptr) { + Log::instance()->error("Uploaded file has no content-type header"); + request->send(400, "text/plain", "no content-type header"); + return; + } if (contentType->value() != "image/jpeg" && contentType->value() != "image/gif") { Log::instance()->error("Uploaded file has unhandled content-type: %s\n", contentType->value()); request->send(400, "text/plain", "unhandled content type"); diff --git a/src/modes/media.h b/src/modes/media.h index c315d39..a2cd253 100644 --- a/src/modes/media.h +++ b/src/modes/media.h @@ -19,7 +19,7 @@ class Media : public Renderer { MEDIA_TYPE_IMAGE = 1, MEDIA_TYPE_GIF = 2, } media_type; - Decoder *current; + Decoder *current = nullptr; media_type mediaType = MEDIA_TYPE_NONE; bool checkSize(AsyncWebServerRequest*, size_t); From 24f50108ee5f57c7410ec677b72c7d95954ac114 Mon Sep 17 00:00:00 2001 From: le0m Date: Tue, 2 Jun 2026 23:41:45 +0200 Subject: [PATCH 07/11] fix: more guards and better string handling in map mode --- src/modes/map.cpp | 84 +++++++++++++++++++++++++++-------------------- src/modes/map.h | 4 +-- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/modes/map.cpp b/src/modes/map.cpp index 2518d1c..d0d5acd 100644 --- a/src/modes/map.cpp +++ b/src/modes/map.cpp @@ -83,6 +83,12 @@ void Map::initServer(AsyncWebServer *server) { [&](AsyncWebServerRequest *request) { Log::instance()->info("Map received\n"); const AsyncWebHeader *contentType = request->getHeader("Content-Type"); + if (contentType == nullptr) { + Log::instance()->error("Uploaded map has no content-type header"); + request->send(400, "text/plain", "no content-type header"); + + return; + } if (contentType->value() != "image/jpeg") { Log::instance()->error("Uploaded map has unhandled content-type: %s\n", contentType->value()); request->send(400, "text/plain", "unhandled content type"); @@ -97,6 +103,12 @@ void Map::initServer(AsyncWebServer *server) { nullptr, [&](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { const AsyncWebHeader *contentType = request->getHeader("Content-Type"); + if (contentType == nullptr) { + Log::instance()->error("Uploaded map has no content-type header"); + request->send(400, "text/plain", "no content-type header"); + + return; + } if (contentType->value() != "image/jpeg") { Log::instance()->error("Uploaded map has unhandled content-type: %s\n", contentType->value()); request->send(400, "text/plain", "unhandled content type"); @@ -218,52 +230,47 @@ std::array Map::getPositionFromAPI() { http.setReuse(false); http.setConnectTimeout(10000); http.setTimeout(60000); - const char *u = config["url"].as(); - const char *m = config["method"].as(); - const char *r = config["regex"].as(); - const char *b = config["body"].as(); + String u = config["url"].as(); + String m = config["method"].as(); + String r = config["regex"].as(); + String b = config["body"].as(); - if (u == nullptr || strcmp(u, "") == 0) { + if (u.isEmpty()) { Log::instance()->error("Position API URL is not set\n"); return pos; } - if (r == nullptr || strcmp(r, "") == 0) { + if (r.isEmpty()) { Log::instance()->error("Position API regexp is not set\n"); return pos; } - if (m == nullptr || strcmp(m, "") == 0) { + if (m.isEmpty()) { m = "GET"; } // Apply query params for GET requests - if (strcasecmp(m, "GET") == 0 && b != nullptr && strcmp(b, "") != 0) { - char urlBuffer[128]; - + if (m.equalsIgnoreCase("GET") && !b.isEmpty()) { // Need a "/" after hostname, otherwise the query parameters end up in the DNS query - size_t urlLen = strlen(u); - if (u[urlLen - 1] == '/') { - snprintf(urlBuffer, sizeof(urlBuffer), "%s%s", u, b); + if (u.endsWith("/")) { + u += b; } else { - snprintf(urlBuffer, sizeof(urlBuffer), "%s/%s", u, b); + u += "/" + b; } - - u = urlBuffer; } - http.begin(client, u); + http.begin(client, u.c_str()); // Apply headers - const char *hs = config["headers"].as(); - if (hs != nullptr && strcmp(hs, "") != 0 && !setHeaders(http, hs)) { + String hs = config["headers"].as(); + if (!hs.isEmpty() && !setHeaders(http, hs.c_str())) { return pos; } // Send request int code; - if (strcasecmp(m, "POST") == 0) { - code = http.POST((b != nullptr && strcmp(b, "") != 0) ? b : ""); + if (m.equalsIgnoreCase("POST")) { + code = http.POST(b); } else { code = http.GET(); } @@ -274,7 +281,8 @@ std::array Map::getPositionFromAPI() { } // Match regex - pos = matchRegex(http.getString().c_str(), r); + String resp = http.getString(); + pos = matchRegex(resp.c_str(), r.c_str()); return pos; }; @@ -282,21 +290,24 @@ std::array Map::getPositionFromAPI() { std::array Map::getPosition() { std::array pos = { 0, 0 }; if ( - strcmp(config["url"].as(), "") != 0 - && strcmp(config["method"].as(), "") != 0 - && strcmp(config["regex"].as(), "") != 0 + config["url"].as() != "" + && config["method"].as() != "" + && config["regex"].as() != "" ) { pos = getPositionFromAPI(); - Log::instance()->info("Position from API (%s): %f, %f\n", config["url"].as(), pos[0], pos[1]); + String url = config["url"].as(); + Log::instance()->info("Position from API (%s): %f, %f\n", url.c_str(), pos[0], pos[1]); } // default to static position, if available if ( pos[0] == 0 && pos[1] == 0 - && strcmp(config["latitude"].as(), "") != 0 - && strcmp(config["longitude"].as(), "") != 0 + && config["latitude"].as() != "" + && config["longitude"].as() != "" ) { - pos[0] = strtod(config["latitude"].as(), NULL); - pos[1] = strtod(config["longitude"].as(), NULL); + String lat = config["latitude"].as(); + String lon = config["longitude"].as(); + pos[0] = strtod(lat.c_str(), NULL); + pos[1] = strtod(lon.c_str(), NULL); Log::instance()->info("Position from configuration: %f, %f\n", pos[0], pos[1]); } @@ -379,14 +390,16 @@ void Map::render(MatrixPanel_I2S_DMA *display) { }; void* Map::openFile(const char *path, int32_t *size) { - File *file = new File(); - *file = LittleFS.open(path); - if (file) { - *size = file->size(); + File *f = new File(); + *f = LittleFS.open(path); + if (*f) { + *size = f->size(); - return static_cast(file); + return static_cast(f); } + delete f; + return NULL; }; @@ -394,6 +407,7 @@ void Map::closeFile(void *pHandle) { File *f = static_cast(pHandle); if (f != NULL) { f->close(); + delete f; } }; diff --git a/src/modes/map.h b/src/modes/map.h index 6f26e6b..abc0cd9 100644 --- a/src/modes/map.h +++ b/src/modes/map.h @@ -18,7 +18,7 @@ class Map : public Renderer { private: typedef struct drawUserData { MatrixPanel_I2S_DMA *display; - uint16_t *position; + uint8_t *position; uint8_t size; uint16_t color; uint16_t track; @@ -28,7 +28,7 @@ class Map : public Renderer { ulong lastRender = 0; ulong lastCropUpdate = 0; uint16_t cropArea[2]; - uint16_t cropPosition[2]; + uint8_t cropPosition[2]; bool isUpdating = false; bool isOpen = false; JsonDocument config; From b199a9d9b0b67cd759dae3665aa08818c04e797e Mon Sep 17 00:00:00 2001 From: le0m Date: Tue, 2 Jun 2026 23:50:19 +0200 Subject: [PATCH 08/11] fix: proper file handling --- src/decoders/gif.cpp | 13 ++++++++----- src/decoders/jpeg.cpp | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/decoders/gif.cpp b/src/decoders/gif.cpp index 54a5257..76ca260 100644 --- a/src/decoders/gif.cpp +++ b/src/decoders/gif.cpp @@ -74,14 +74,16 @@ void DecoderGIF::renderFrame(MatrixPanel_I2S_DMA *display) { }; void* DecoderGIF::openFile(const char *fname, int32_t *pSize) { - File *file = new File(); - *file = LittleFS.open(fname); - if (file) { - *pSize = file->size(); + File *f = new File(); + *f = LittleFS.open(fname); + if (*f) { + *pSize = f->size(); - return static_cast(file); + return static_cast(f); } + delete f; + return NULL; }; @@ -89,6 +91,7 @@ void DecoderGIF::closeFile(void *pHandle) { File *f = static_cast(pHandle); if (f != NULL) { f->close(); + delete f; } }; diff --git a/src/decoders/jpeg.cpp b/src/decoders/jpeg.cpp index 413d0df..fdfc857 100644 --- a/src/decoders/jpeg.cpp +++ b/src/decoders/jpeg.cpp @@ -66,14 +66,16 @@ void DecoderJPEG::renderFrame(MatrixPanel_I2S_DMA *display) { }; void* DecoderJPEG::openFile(const char *path, int32_t *size) { - File *file = new File(); - *file = LittleFS.open(path); - if (file) { - *size = file->size(); + File *f = new File(); + *f = LittleFS.open(path); + if (*f) { + *size = f->size(); - return static_cast(file); + return static_cast(f); } + delete f; + return NULL; }; @@ -81,6 +83,7 @@ void DecoderJPEG::closeFile(void *pHandle) { File *f = static_cast(pHandle); if (f != NULL) { f->close(); + delete f; } }; From 1b4fcbd0f245a192808a45486896803a6cc3c906 Mon Sep 17 00:00:00 2001 From: le0m Date: Wed, 3 Jun 2026 00:23:44 +0200 Subject: [PATCH 09/11] fix: life memory handling --- src/modes/life.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modes/life.cpp b/src/modes/life.cpp index c39cd5e..f51272f 100644 --- a/src/modes/life.cpp +++ b/src/modes/life.cpp @@ -14,7 +14,13 @@ bool Life::open() { return true; } - world = (Cell*)malloc(width * height * sizeof(Cell)); + world = (Cell*)calloc(width * height, sizeof(Cell)); + if (world == nullptr) { + Log::instance()->error("Error allocating world\n"); + + return false; + } + generation = 0; isOpen = true; @@ -29,6 +35,7 @@ bool Life::close() { isOpen = false; delay(50); // wait for possible render() execution to finish free(world); + world = nullptr; generation = 0; return true; From f17bff3953fe798487de76d582406383fe037384 Mon Sep 17 00:00:00 2001 From: le0m Date: Wed, 3 Jun 2026 00:49:59 +0200 Subject: [PATCH 10/11] fix: check qr code size --- src/modes/qrcode.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/modes/qrcode.cpp b/src/modes/qrcode.cpp index ab389d2..86bb0d4 100644 --- a/src/modes/qrcode.cpp +++ b/src/modes/qrcode.cpp @@ -37,6 +37,13 @@ bool QRCode::setText(const char* str) { return false; } + int size = qrcodegen_getSize(qr0); + if (size + QR_BORDER_PX * 2 > width || size + QR_BORDER_PX * 2 > height) { + Log::instance()->error("QR code too large for panel: %d (border %d)\n", size, QR_BORDER_PX); + + return false; + } + strncpy(text, str, QR_TEXT_MAX_LENGTH - 1); text[QR_TEXT_MAX_LENGTH - 1] = '\0'; changed = true; @@ -52,8 +59,8 @@ void QRCode::render(MatrixPanel_I2S_DMA* display) { } int size = qrcodegen_getSize(qr0); - uint8_t startX = (width - size) / 2; - uint8_t startY = (height - size) / 2; + int startX = (width - size) / 2; + int startY = (height - size) / 2; display->fillRect(startX - QR_BORDER_PX, startY - QR_BORDER_PX, size + QR_BORDER_PX * 2, size + QR_BORDER_PX * 2, display->color565(255, 255, 255)); for (uint8_t y = 0; y < size; y++) { for (uint8_t x = 0; x < size; x++) { From 7deceab041e56f094a6e91a54b34467936ed4fae Mon Sep 17 00:00:00 2001 From: le0m Date: Wed, 3 Jun 2026 01:42:32 +0200 Subject: [PATCH 11/11] fix: guards and string handling --- src/main.cpp | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d4ea30a..0b88090 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ HUB75_I2S_CFG ledConfig( ); MatrixPanel_I2S_DMA *display = nullptr; +bool setupOk = false; OTA ota(1000); draw_mode drawMode = DRAW_MODE_NONE; WiFiController wifi; @@ -30,6 +31,12 @@ Renderer *currentMode = nullptr; void updateQrText() { const char* url = wifi.getUrl(); + if (url == nullptr) { + Log::instance()->error("Error getting URL for QR code\n"); + + return; + } + Log::instance()->info("QR code URL: %s\n", url); if (url[0] != '\0' && !qr.setText(url)) { Log::instance()->error("Error encoding IP to QR code\n"); @@ -70,7 +77,9 @@ bool changeMode(draw_mode newMode) { drawMode = newMode; Log::instance()->info("Changed mode to %d\n", drawMode); - display->clearScreen(); + if (display != nullptr) { + display->clearScreen(); + } if (newMode == DRAW_MODE_QR) { updateQrText(); @@ -80,16 +89,16 @@ bool changeMode(draw_mode newMode) { }; void updateConfig(JsonDocument& newConfig) { - const char* newSsid = newConfig["wifi"]["ssid"].as(); - const char* newPassword = newConfig["wifi"]["password"].as(); - const char* currentSsid = conf.current["wifi"]["ssid"].as(); - const char* currentPassword = conf.current["wifi"]["password"].as(); + String newSsid = newConfig["wifi"]["ssid"].as(); + String newPassword = newConfig["wifi"]["password"].as(); + String currentSsid = conf.current["wifi"]["ssid"].as(); + String currentPassword = conf.current["wifi"]["password"].as(); - if (strcmp(newSsid, currentSsid) != 0 || strcmp(newPassword, currentPassword) != 0) { + if (!newSsid.equals(currentSsid) || !newPassword.equals(currentPassword)) { Log::instance()->info("WiFi configuration changed, reconnecting\n"); - wifi.connect(newSsid, newPassword); + wifi.connect(newSsid.c_str(), newPassword.c_str()); } - if (newConfig["panel"]["brightness"].as() != conf.current["panel"]["brightness"].as()) { + if (display != nullptr && newConfig["panel"]["brightness"].as() != conf.current["panel"]["brightness"].as()) { Log::instance()->info("Panel brightness changed: %d\n", newConfig["panel"]["brightness"].as()); display->setBrightness(newConfig["panel"]["brightness"].as()); } @@ -122,6 +131,11 @@ void setup() { // Initialize LED matrix display = setupLED(ledConfig); + if (display == nullptr) { + Log::instance()->error("Error setting up LED matrix\n"); + + return; + } display->setBrightness(conf.current["panel"]["brightness"].as()); // Initialize gyroscope @@ -130,9 +144,12 @@ void setup() { } // Initialize WiFi - if (!wifi.connect(conf.current["wifi"]["ssid"].as(), conf.current["wifi"]["password"].as())) { + String ssid = conf.current["wifi"]["ssid"].as(); + String password = conf.current["wifi"]["password"].as(); + if (!wifi.connect(ssid.c_str(), password.c_str())) { return; } + updateQrText(); // Initialize log WebSocket @@ -179,14 +196,19 @@ void setup() { server.begin(); // Filesystem::tree("/", 0); + setupOk = true; }; ulong lastModePoll = 0; void loop() { + if (!setupOk) { + return; + } + ota.loop(); - if (lastModePoll + 1000 < millis()) { + if (millis() - lastModePoll >= 1000) { lastModePoll = millis(); if (changeMode(selector.getMode())) { return;