From 7e20aa65e1df03e99bd786b8662a3a0270f7d3ee Mon Sep 17 00:00:00 2001 From: Stefan Ostermann Date: Tue, 7 Oct 2025 21:35:39 +0200 Subject: [PATCH] cleanups, memory savings --- src/DirectoryNode.cpp | 15 ++++---- src/DirectoryNode.h | 2 + src/main.cpp | 84 ++++++++++++++-------------------------- src/main.h | 19 +++++++++ web/script.js | 90 ------------------------------------------- 5 files changed, 56 insertions(+), 154 deletions(-) diff --git a/src/DirectoryNode.cpp b/src/DirectoryNode.cpp index 81d61a5..872046a 100644 --- a/src/DirectoryNode.cpp +++ b/src/DirectoryNode.cpp @@ -470,6 +470,11 @@ void DirectoryNode::buildFlatMP3List(std::vector } } +const size_t DirectoryNode::getNumOfFiles() +{ + return subdirectories.size(); +} + DirectoryNode *DirectoryNode::advanceToNextMP3(const String *currentGlobal) { bool useFirst = false; @@ -537,7 +542,6 @@ DirectoryNode *DirectoryNode::advanceToNextMP3(const String *currentGlobal) void DirectoryNode::streamDirectoryHTML(Print &out) const { if (name == "/") { out.println(F("")); - delay(0); } } diff --git a/src/DirectoryNode.h b/src/DirectoryNode.h index d2c1012..990b5b9 100644 --- a/src/DirectoryNode.h +++ b/src/DirectoryNode.h @@ -35,6 +35,8 @@ public: const std::vector& getSubdirectories() const; const std::vector& getMP3Files() const; + const size_t getNumOfFiles(); + void setCurrentPlaying(const String* mp3File); const String* getCurrentPlaying() const; const uint16_t getCurrentPlayingId() const; diff --git a/src/main.cpp b/src/main.cpp index ef948ff..5f16121 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,39 +28,6 @@ - -/* Simple spinlock using older GCC sync builtins (no libatomic required). - sd_lock_acquire() will block (with a small delay) until the lock is free. - sd_lock_release() releases the lock. This is sufficient for short SD ops. */ -static inline void sd_lock_acquire() -{ - while (__sync_lock_test_and_set(&sd_lock_flag, 1)) - { - delay(1); - } -} - -static inline void sd_lock_release() -{ - __sync_lock_release(&sd_lock_flag); -} - -volatile uint8_t dir_lock_flag = 0; -/* Lightweight spinlock to guard directory tree access (rootNode) */ -static inline void dir_lock_acquire() -{ - while (__sync_lock_test_and_set(&dir_lock_flag, 1)) - { - delay(1); - } -} -static inline void dir_lock_release() -{ - __sync_lock_release(&dir_lock_flag); -} - - - // webrequest_blockings is a simple watchdog counter that tracks how long at least one HTTP request has been “active” (not yet disconnected) according to the AsyncWebServer. int webrequest_blockings = 0; @@ -248,9 +215,9 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, logBuffer.clear(); // Rebuild directory tree to include new file (guarded) - dir_lock_acquire(); + sd_lock_acquire(); rootNode.buildDirectoryTree("/"); - dir_lock_release(); + sd_lock_release(); request->send(200, txt_plain, "Upload successful"); } @@ -275,9 +242,9 @@ void handleMoveFile(AsyncWebServerRequest *request) sd_lock_release(); Serial.println("Moved file: " + from + " to " + to); // Rebuild directory tree to update file list (guarded) - dir_lock_acquire(); + sd_lock_acquire(); rootNode.buildDirectoryTree("/"); - dir_lock_release(); + sd_lock_release(); request->send(200, txt_plain, F("File moved successfully.")); } else @@ -300,9 +267,9 @@ void handleDeleteFile(AsyncWebServerRequest *request) sd_lock_release(); Serial.println("Deleted file: " + filename); // Rebuild directory tree to update file list (guarded) - dir_lock_acquire(); + sd_lock_acquire(); rootNode.buildDirectoryTree("/"); - dir_lock_release(); + sd_lock_release(); request->send(200, txt_plain, "File deleted."); } else @@ -430,14 +397,16 @@ void playSongByRFID(String id) auto it = rfid_map.find(id); if (it == rfid_map.end()) { - Serial.println("Song for UID not found: " + id); + Serial.print(F("Song for UID not found: ")); + Serial.println(id); return; } MappingEntry entry = it->second; if (entry.target.length() == 0) { - Serial.println("Empty mapping target for UID: " + id); + Serial.print(F("Empty mapping target for UID: ")); + Serial.println(id); return; } @@ -544,7 +513,7 @@ void playSongByRFID(String id) } } - Serial.print("Playing mapped target: "); + Serial.print(F("Playing mapped target: ")); Serial.println(mp3File); deactivateRFID(); @@ -552,7 +521,8 @@ void playSongByRFID(String id) if (!playFile(mp3File.c_str())) { - Serial.println("Failed to play mapped file: " + mp3File); + Serial.print(F("Failed to play mapped file: ")); + Serial.println( mp3File); currentNode = nullptr; return; @@ -571,7 +541,7 @@ bool playFile(const char *filename, uint32_t resumeFilePos) { if (filename == nullptr || strlen(filename) == 0) { - Serial.println("filename empty."); + Serial.println(F("filename empty.")); return false; } // Serialize access to SD when audio opens the file (short critical section) @@ -690,7 +660,7 @@ boolean readSongProgress(const char *filename) data.trim(); if (data.length() == 0) { - Serial.println("Progress file empty"); + Serial.println(F("Progress file empty")); return false; } @@ -701,7 +671,8 @@ boolean readSongProgress(const char *filename) if (parsed != 2) { - Serial.println("Failed to parse progress data: " + data); + Serial.print(F("Failed to parse progress data: ")); + Serial.println(data); return false; } @@ -1156,9 +1127,10 @@ void init_webserver() { server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { webreq_enter(); - request->onDisconnect([](){ webreq_exit(); }); + request->onDisconnect([](){ webreq_exit(); sd_lock_release();}); deactivateRFID(); activateSD(); + sd_lock_acquire(); String htmlPath = getSysDir(index_file); if (SD.exists(htmlPath)) { @@ -1186,15 +1158,15 @@ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request) { webreq_enter(); - request->onDisconnect([](){ webreq_exit(); }); + request->onDisconnect([](){ webreq_exit(); sd_lock_release();}); deactivateRFID(); - activateSD(); + activateSD(); // Ensure SD is active and RFID is deactivated while serving files. String cssPath = getSysDir(style_file); if (SD.exists(cssPath)) { uint32_t fsize = 0; - { + { File f = SD.open(cssPath); if (f) { fsize = f.size(); f.close(); } } @@ -1219,9 +1191,10 @@ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) server.on("/script.js", HTTP_GET, [](AsyncWebServerRequest *request) { webreq_enter(); - request->onDisconnect([](){ webreq_exit(); }); + request->onDisconnect([](){ webreq_exit(); sd_lock_release();}); deactivateRFID(); activateSD(); + sd_lock_acquire(); String jsPath = getSysDir(script_file); if (SD.exists(jsPath)) { @@ -1256,21 +1229,20 @@ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) // Stream the response directly from the directory tree to avoid large temporary Strings AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset); #ifdef DEBUG - Serial.printf("Serving /directory heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt); + Serial.printf("Serving /directory heap=%u webreq_cnt=%u numOfFiles=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt, rootNode.getNumOfFiles()); #endif stream->addHeader(hdr_cache_control_key, hdr_cache_control_val); stream->addHeader(hdr_connection_key, hdr_connection_val); // Generate HTML directly into the stream under lock - dir_lock_acquire(); - rootNode.streamDirectoryHTML(*stream); - dir_lock_release(); + rootNode.streamDirectoryHTML(*stream); request->send(stream); }); server.on("/mapping", HTTP_GET, [](AsyncWebServerRequest *request) { webreq_enter(); - request->onDisconnect([](){ webreq_exit(); }); + request->onDisconnect([](){ webreq_exit(); sd_lock_release();}); + sd_lock_acquire(); // Stream mapping to avoid Content-Length mismatches and reduce heap spikes AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset); #ifdef DEBUG diff --git a/src/main.h b/src/main.h index 8d8c439..2018746 100644 --- a/src/main.h +++ b/src/main.h @@ -34,6 +34,8 @@ #define MAX_VOL 15 +//#define DEBUG TRUE + File root; File mp3File; @@ -125,6 +127,23 @@ void dump_byte_array(byte *buffer, byte bufferSize) } } +/* Simple spinlock using older GCC sync builtins (no libatomic required). + sd_lock_acquire() will block (with a small delay) until the lock is free. + sd_lock_release() releases the lock. This is sufficient for short SD ops. */ +static inline void sd_lock_acquire() +{ + while (__sync_lock_test_and_set(&sd_lock_flag, 1)) + { + delay(1); + } +} + +static inline void sd_lock_release() +{ + __sync_lock_release(&sd_lock_flag); +} + + String getRFIDString(byte uidByte[10]) { String uidString = String(uidByte[0]) + " " + String(uidByte[1]) + " " + diff --git a/web/script.js b/web/script.js index 2b92362..e660089 100644 --- a/web/script.js +++ b/web/script.js @@ -326,7 +326,6 @@ function editMapping() { // Validate file before upload function validateFile(file) { var maxSize = 50 * 1024 * 1024; // 50MB limit - var allowedTypes = ['audio/mpeg', 'audio/wav']; var allowedExtensions = ['.mp3']; if (file.size > maxSize) { @@ -499,92 +498,3 @@ function deleteFileOnServer() { xhr.send(); } -/* Ensure the site stylesheet loads reliably — retry loader if necessary - Improved detection: verify a computed style from CSS is applied (safer than just checking stylesheet href). - Retries with exponential backoff and deduplicates link tags we add. */ -(function ensureCssLoaded(){ - var retries = 0; - var maxRetries = 6; - - // Check a computed style that the stylesheet defines. - // .status color in CSS is --muted: #6b7280 -> rgb(107, 114, 128) - function isStyleApplied() { - var el = document.querySelector('.status') || document.querySelector('.topbar'); - if (!el) return false; - try { - var color = getComputedStyle(el).color; - // Expect "rgb(107, 114, 128)" when CSS is applied - if (!color) return false; - // Loose check for the three numeric components to be present - return color.indexOf('107') !== -1 && color.indexOf('114') !== -1 && color.indexOf('128') !== -1; - } catch (e) { - return false; - } - } - - function removeOldRetryLinks() { - var links = Array.prototype.slice.call(document.querySelectorAll('link[data-retry-css]')); - links.forEach(function(l){ l.parentNode.removeChild(l); }); - } - - function tryLoad() { - if (isStyleApplied()) { - console.log('style.css appears applied'); - return; - } - if (retries >= maxRetries) { - console.warn('style.css failed to apply after ' + retries + ' attempts'); - return; - } - retries++; - // Remove previous retry-inserted links to avoid piling them up - removeOldRetryLinks(); - - var link = document.createElement('link'); - link.rel = 'stylesheet'; - link.setAttribute('data-retry-css', '1'); - // cache-busting query to force a fresh fetch when retrying - link.href = 'style.css?cb=' + Date.now(); - var timeout = 800 + retries * 300; // increasing timeout per attempt - - var done = false; - function success() { - if (done) return; - done = true; - // Give browser a short moment to apply rules - setTimeout(function(){ - if (isStyleApplied()) { - console.log('style.css loaded and applied (attempt ' + retries + ')'); - } else { - console.warn('style.css loaded but styles not applied — retrying...'); - setTimeout(tryLoad, timeout); - } - }, 200); - } - - link.onload = success; - link.onerror = function() { - if (done) return; - done = true; - console.warn('style.css load error (attempt ' + retries + '), retrying...'); - setTimeout(tryLoad, timeout); - }; - - // Append link to head - document.head.appendChild(link); - - // Safety check: if onload/onerror doesn't fire, verify computed style after timeout - setTimeout(function(){ - if (done) return; - if (isStyleApplied()) { - console.log('style.css appears applied (delayed check)'); - } else { - console.warn('style.css still not applied after timeout (attempt ' + retries + '), retrying...'); - setTimeout(tryLoad, timeout); - } - }, timeout + 300); - } - - // Start after a short delay to let the browser initiate initial requests - setTimeout(tryLoad, 150); -})();