[ai] memory optimizations

This commit is contained in:
Stefan Ostermann 2025-11-02 21:51:35 +01:00
parent fd40b663a0
commit b97eb79b91
1 changed files with 72 additions and 64 deletions

View File

@ -36,7 +36,7 @@ void activateSD()
if (!SD.begin(CS_SDCARD)) if (!SD.begin(CS_SDCARD))
{ {
Serial.println("SD initialization failed!"); Serial.println(F("SD initialization failed!"));
} }
SDActive = true; SDActive = true;
} }
@ -112,15 +112,23 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
activateSD(); activateSD();
// Check if file already exists and create backup name if needed // Check if file already exists and create backup name if needed
String filepath = "/" + filename; String filepath;
filepath.reserve(1 + filename.length());
filepath = "/";
filepath += filename;
if (SD.exists(filepath)) if (SD.exists(filepath))
{ {
String baseName = filename.substring(0, filename.lastIndexOf('.')); String baseName = filename.substring(0, filename.lastIndexOf('.'));
String extension = filename.substring(filename.lastIndexOf('.')); String extension = filename.substring(filename.lastIndexOf('.'));
int counter = 1; int counter = 1;
filepath.reserve(1 + baseName.length() + 1 + 10 + extension.length());
do do
{ {
filepath = "/" + baseName + "_" + String(counter) + extension; filepath = "/";
filepath += baseName;
filepath += "_";
filepath += counter;
filepath += extension;
counter++; counter++;
} while (SD.exists(filepath) && counter < 100); } while (SD.exists(filepath) && counter < 100);
@ -162,13 +170,13 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
// ensure we close while holding the lock to keep SD state consistent // ensure we close while holding the lock to keep SD state consistent
request->_tempFile.close(); request->_tempFile.close();
sd_lock_release(); sd_lock_release();
request->send(500, txt_plain, "Write error"); request->send_P(500, txt_plain, PSTR("Write error"));
return; return;
} }
// Flush data periodically to ensure it's written // Flush data periodically to ensure it's written
if (index % 2048 == 0) if (index % buffer_size == 0)
{ // Flush every 2KB { // Flush every so often
request->_tempFile.flush(); request->_tempFile.flush();
} }
sd_lock_release(); sd_lock_release();
@ -186,7 +194,7 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
Serial.print(F("Upload Complete: ")); Serial.print(F("Upload Complete: "));
Serial.print(filename); Serial.print(filename);
Serial.print(", size: "); Serial.print(F(", size: "));
Serial.println(humanReadableSize(index + len)); Serial.println(humanReadableSize(index + len));
// Rebuild directory tree to include new file (guarded) // Rebuild directory tree to include new file (guarded)
@ -194,11 +202,11 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
rootNode.buildDirectoryTree("/"); rootNode.buildDirectoryTree("/");
sd_lock_release(); sd_lock_release();
request->send(200, txt_plain, "Upload successful"); request->send_P(200, txt_plain, PSTR("Upload successful"));
} }
else else
{ {
request->send(500, txt_plain, "Upload failed"); request->send_P(500, txt_plain, PSTR("Upload failed"));
} }
} }
} }
@ -215,7 +223,7 @@ void handleMoveFile(AsyncWebServerRequest *request)
sd_lock_acquire(); sd_lock_acquire();
SD.rename(from, to); SD.rename(from, to);
sd_lock_release(); sd_lock_release();
Serial.println("Moved file: " + from + " to " + to); Serial.print(F("Moved file: ")); Serial.print(from); Serial.print(F(" to ")); Serial.println(to);
// Rebuild directory tree to update file list (guarded) // Rebuild directory tree to update file list (guarded)
sd_lock_acquire(); sd_lock_acquire();
rootNode.buildDirectoryTree("/"); rootNode.buildDirectoryTree("/");
@ -224,8 +232,8 @@ void handleMoveFile(AsyncWebServerRequest *request)
} }
else else
{ {
Serial.println("File not found: " + from); Serial.print(F("File not found: ")); Serial.println(from);
request->send(404, txt_plain, "File not found."); request->send_P(404, txt_plain, PSTR("File not found."));
} }
} }
@ -240,17 +248,17 @@ void handleDeleteFile(AsyncWebServerRequest *request)
sd_lock_acquire(); sd_lock_acquire();
SD.remove(filename.c_str()); SD.remove(filename.c_str());
sd_lock_release(); sd_lock_release();
Serial.println("Deleted file: " + filename); Serial.print(F("Deleted file: ")); Serial.println(filename);
// Rebuild directory tree to update file list (guarded) // Rebuild directory tree to update file list (guarded)
sd_lock_acquire(); sd_lock_acquire();
rootNode.buildDirectoryTree("/"); rootNode.buildDirectoryTree("/");
sd_lock_release(); sd_lock_release();
request->send(200, txt_plain, "File deleted."); request->send(200, txt_plain, F("File deleted."));
} }
else else
{ {
Serial.println("File not found: " + filename); Serial.print(F("File not found: ")); Serial.println(filename);
request->send(404, txt_plain, "File not found."); request->send_P(404, txt_plain, PSTR("File not found."));
} }
} }
@ -288,12 +296,12 @@ void playSongById(uint16_t id, uint32_t continueSeconds = 0)
if (mp3File.length() == 0) if (mp3File.length() == 0)
{ {
currentNode = nullptr; currentNode = nullptr;
Serial.print("Empty file path for ID: "); Serial.print(F("Empty file path for ID: "));
Serial.println(id); Serial.println(id);
return; return;
} }
Serial.print("Playing by ID: "); Serial.print(F("Playing by ID: "));
Serial.println(id); Serial.println(id);
Serial.println(mp3File); Serial.println(mp3File);
@ -314,7 +322,7 @@ void playSongById(uint16_t id, uint32_t continueSeconds = 0)
} }
} }
void playSongByName(String song) void playSongByName(const String &song)
{ {
if (song.length() == 0) if (song.length() == 0)
{ {
@ -348,24 +356,24 @@ void playSongByName(String song)
return; return;
} }
Serial.println("Playing song: " + mp3File); Serial.print(F("Playing song: ")); Serial.println(mp3File);
deactivateRFID(); deactivateRFID();
activateSD(); activateSD();
if (!playFile(mp3File.c_str())) if (!playFile(mp3File.c_str()))
{ {
Serial.println("Failed to play file: " + mp3File); Serial.print(F("Failed to play file: ")); Serial.println(mp3File);
currentNode = nullptr; currentNode = nullptr;
return; return;
} }
} }
void playSongByPath(String path) void playSongByPath(const String &path)
{ {
playFile(path.c_str()); playFile(path.c_str());
} }
void playSongByRFID(String id) void playSongByRFID(const String &id)
{ {
if (id.length() == 0) if (id.length() == 0)
{ {
@ -463,7 +471,7 @@ void playSongByRFID(String id)
folderFlatIndex = 0; folderFlatIndex = 0;
DirectoryNode *startNode = folderFlatList[0].first; DirectoryNode *startNode = folderFlatList[0].first;
int fileIdx = folderFlatList[0].second; int fileIdx = folderFlatList[0].second;
Serial.print("Shuffle start: "); Serial.print(F("Shuffle start: "));
Serial.println(startNode->getMP3Files()[fileIdx]); Serial.println(startNode->getMP3Files()[fileIdx]);
startNode->setCurrentPlaying(startNode->getMP3Files()[fileIdx]); startNode->setCurrentPlaying(startNode->getMP3Files()[fileIdx]);
currentNode = startNode; currentNode = startNode;
@ -553,7 +561,7 @@ void playNextMp3()
currentNode->setSecondsPlayed(0); currentNode->setSecondsPlayed(0);
} }
Serial.print("Advancing to "); Serial.print(F("Advancing to "));
String mp3File = currentNode->getCurrentPlaying(); String mp3File = currentNode->getCurrentPlaying();
// FIXME crash here if last song. // FIXME crash here if last song.
if (mp3File.isEmpty()) if (mp3File.isEmpty())
@ -598,12 +606,12 @@ void writeSongProgress(const char *filename, uint16_t id, uint32_t seconds)
file.println(seconds); file.println(seconds);
file.close(); file.close();
#ifdef DEBUG #ifdef DEBUG
Serial.println("Progress written: ID " + String(id) + ", s " + String(seconds)); Serial.print(F("Progress written: ID ")); Serial.print(id); Serial.print(F(", s ")); Serial.println(seconds);
#endif #endif
} }
else else
{ {
Serial.print("Error opening file for writing: "); Serial.print(F("Error opening file for writing: "));
Serial.println(filename); Serial.println(filename);
} }
} }
@ -614,7 +622,7 @@ boolean readSongProgress(const char *filename)
if (!file) if (!file)
{ {
Serial.print("Error opening file for reading: "); Serial.print(F("Error opening file for reading: "));
Serial.println(filename); Serial.println(filename);
return false; return false;
} }
@ -660,13 +668,13 @@ boolean readSongProgress(const char *filename)
// Validate ranges before assignment // Validate ranges before assignment
if (tempId < 0 || tempId > 65535) if (tempId < 0 || tempId > 65535)
{ {
Serial.println("Invalid song in progress: " + String(tempId)); Serial.print(F("Invalid song in progress: ")); Serial.println(tempId);
return false; return false;
} }
if (tempSeconds > 4294967295UL) if (tempSeconds > 4294967295UL)
{ {
Serial.println("Invalid seconds in progress: " + String(tempSeconds)); Serial.print(F("Invalid seconds in progress: ")); Serial.println(tempSeconds);
return false; return false;
} }
@ -674,8 +682,8 @@ boolean readSongProgress(const char *filename)
currentSongSeconds = (uint32_t)tempSeconds; currentSongSeconds = (uint32_t)tempSeconds;
#ifdef DEBUG #ifdef DEBUG
Serial.println("Data read from file: " + data); Serial.print(F("Data read from file: ")); Serial.println(data);
Serial.println("Parsed ID: " + String(currentSongId) + ", s: " + String(currentSongSeconds)); Serial.print(F("Parsed ID: ")); Serial.print(currentSongId); Serial.print(F(", s: ")); Serial.println(currentSongSeconds);
#endif #endif
return true; return true;
@ -683,7 +691,7 @@ boolean readSongProgress(const char *filename)
// Function to save the rfid_map to the mapping file // Function to save the rfid_map to the mapping file
void saveMappingToFile(const String filename) void saveMappingToFile(const String &filename)
{ {
File file = SD.open(filename, FILE_WRITE); File file = SD.open(filename, FILE_WRITE);
if (file) if (file)
@ -728,15 +736,15 @@ void editMapping(AsyncWebServerRequest *request)
rfid_map[rfid] = MappingEntry(song, mode); rfid_map[rfid] = MappingEntry(song, mode);
saveMappingToFile(getSysDir(mapping_file)); saveMappingToFile(getSysDir(mapping_file));
request->send(200, txt_plain, "Mapping updated"); request->send_P(200, txt_plain, PSTR("Mapping updated"));
} }
else else
{ {
request->send(400, txt_plain, "Invalid parameters"); request->send_P(400, txt_plain, PSTR("Invalid parameters"));
} }
} }
void readDataFromFile(String filename) void readDataFromFile(const String &filename)
{ {
File file = SD.open(filename); File file = SD.open(filename);
@ -769,7 +777,7 @@ void readDataFromFile(String filename)
mode = mstr.charAt(0); mode = mstr.charAt(0);
} }
#ifdef DEBUG #ifdef DEBUG
Serial.println("found rfid mapping for " + target + " mode " + String(mode)); Serial.print(F("found rfid mapping for ")); Serial.print(target); Serial.print(F(" mode ")); Serial.println(mode);
#endif #endif
// Add key-value pair to the map // Add key-value pair to the map
rfid_map[key] = MappingEntry(target, mode); rfid_map[key] = MappingEntry(target, mode);
@ -943,7 +951,7 @@ void stop()
{ {
if (audio.isRunning()) if (audio.isRunning())
{ {
Serial.println("stopping audio."); Serial.println(F("stopping audio."));
audio.stopSong(); audio.stopSong();
if (currentNode != NULL) if (currentNode != NULL)
{ {
@ -1003,14 +1011,14 @@ void previous()
return; return;
} }
Serial.print("previous(): Current song: "); Serial.print(F("previous(): Current song: "));
Serial.println(currentSong); Serial.println(currentSong);
// Use audio library's current time instead of tracked seconds for more accuracy // Use audio library's current time instead of tracked seconds for more accuracy
uint32_t currentAudioTime = audio.getAudioCurrentTime(); uint32_t currentAudioTime = audio.getAudioCurrentTime();
Serial.print("previous(): Current audio time: "); Serial.print(F("previous(): Current audio time: "));
Serial.print(currentAudioTime); Serial.print(currentAudioTime);
Serial.println(" seconds"); Serial.println(F(" seconds"));
// Try to go to previous within current directory first // Try to go to previous within current directory first
DirectoryNode *newNode = currentNode->goToPreviousMP3(2); // Use 2 second threshold DirectoryNode *newNode = currentNode->goToPreviousMP3(2); // Use 2 second threshold
@ -1023,14 +1031,14 @@ void previous()
if (currentSong == newSong && currentAudioTime > 2) if (currentSong == newSong && currentAudioTime > 2)
{ {
// Restart current song if it's been playing for more than 2 seconds // Restart current song if it's been playing for more than 2 seconds
Serial.println("previous(): Restarting current song"); Serial.println(F("previous(): Restarting current song"));
audio.setAudioPlayPosition(0); audio.setAudioPlayPosition(0);
currentNode->setSecondsPlayed(0); currentNode->setSecondsPlayed(0);
} }
else if (currentSong != newSong) else if (currentSong != newSong)
{ {
// Move to previous song in same directory // Move to previous song in same directory
Serial.print("previous(): Moving to previous song in directory: "); Serial.print(F("previous(): Moving to previous song in directory: "));
Serial.println(newSong); Serial.println(newSong);
currentNode = newNode; currentNode = newNode;
stop(); stop();
@ -1079,7 +1087,7 @@ void previous()
void audio_eof_mp3(const char *info) void audio_eof_mp3(const char *info)
{ {
Serial.println("audio file ended."); Serial.println(F("audio file ended."));
if (prepareSleepMode) if (prepareSleepMode)
return; return;
@ -1169,7 +1177,7 @@ void readRFID()
stop(); stop();
lastUid = newUid; lastUid = newUid;
Serial.print("Card UID: "); Serial.print(F("Card UID: "));
Serial.println(lastUid); Serial.println(lastUid);
// rfid.PICC_DumpDetailsToSerial(&(rfid.uid)); // rfid.PICC_DumpDetailsToSerial(&(rfid.uid));
@ -1278,7 +1286,7 @@ void init_webserver() {
webreq_enter(); webreq_enter();
request->onDisconnect([](){ webreq_exit(); }); request->onDisconnect([](){ webreq_exit(); });
// Stream the response directly from the directory tree to avoid large temporary Strings // Stream the response directly from the directory tree to avoid large temporary Strings
AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, 512); AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, buffer_size);
#ifdef DEBUG #ifdef DEBUG
Serial.printf("Serving /directory heap=%u webreq_cnt=%u numOfFiles=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt, rootNode.getNumOfFiles()); Serial.printf("Serving /directory heap=%u webreq_cnt=%u numOfFiles=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt, rootNode.getNumOfFiles());
#endif #endif
@ -1294,7 +1302,7 @@ void init_webserver() {
webreq_enter(); webreq_enter();
request->onDisconnect([](){ webreq_exit();}); request->onDisconnect([](){ webreq_exit();});
// Stream mapping to avoid building a large HTML String // Stream mapping to avoid building a large HTML String
AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, 512); AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, buffer_size);
#ifdef DEBUG #ifdef DEBUG
Serial.printf("Serving /mapping heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt); Serial.printf("Serving /mapping heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
#endif #endif
@ -1324,35 +1332,35 @@ void init_webserver() {
{ {
webreq_enter(); webreq_enter();
request->onDisconnect([](){ webreq_exit(); }); request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "start"); request->send_P(200, txt_plain, PSTR("start"));
start(); }); start(); });
server.on("/toggleplaypause", HTTP_GET, [](AsyncWebServerRequest *request) server.on("/toggleplaypause", HTTP_GET, [](AsyncWebServerRequest *request)
{ {
webreq_enter(); webreq_enter();
request->onDisconnect([](){ webreq_exit(); }); request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "toggleplaypause"); request->send_P(200, txt_plain, PSTR("toggleplaypause"));
togglePlayPause(); }); togglePlayPause(); });
server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request) server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request)
{ {
webreq_enter(); webreq_enter();
request->onDisconnect([](){ webreq_exit(); }); request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "stop"); request->send_P(200, txt_plain, PSTR("stop"));
stop(); }); stop(); });
server.on("/next", HTTP_GET, [](AsyncWebServerRequest *request) server.on("/next", HTTP_GET, [](AsyncWebServerRequest *request)
{ {
webreq_enter(); webreq_enter();
request->onDisconnect([](){ webreq_exit(); }); request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "next"); request->send_P(200, txt_plain, PSTR("next"));
next(); }); next(); });
server.on("/previous", HTTP_GET, [](AsyncWebServerRequest *request) server.on("/previous", HTTP_GET, [](AsyncWebServerRequest *request)
{ {
webreq_enter(); webreq_enter();
request->onDisconnect([](){ webreq_exit(); }); request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "previous"); request->send_P(200, txt_plain, PSTR("previous"));
previous(); }); previous(); });
server.on("/playbyid", HTTP_GET, id_song_action); server.on("/playbyid", HTTP_GET, id_song_action);
@ -1434,14 +1442,14 @@ void setup()
if (continuePlaying) if (continuePlaying)
{ {
Serial.print("deleting "); Serial.print(F("deleting "));
Serial.println(progressPath); Serial.println(progressPath);
SD.remove(progressPath); SD.remove(progressPath);
} }
deactivateSD(); deactivateSD();
activateRFID(); activateRFID();
Serial.println("RFID"); Serial.println(F("RFID"));
// Init MFRC522 // Init MFRC522
// Init SPI bus // Init SPI bus
@ -1466,7 +1474,7 @@ void setup()
// Optimize audio buffer size to save memory (ESP32-audioI2S optimization) // Optimize audio buffer size to save memory (ESP32-audioI2S optimization)
audio.setBufferSize(8192); // Reduced from default large buffer (saves 40-600KB!) audio.setBufferSize(8192); // Reduced from default large buffer (saves 40-600KB!)
Serial.println("Audio init"); Serial.println(F("Audio init"));
lastVoltage = getBatteryVoltageMv(); lastVoltage = getBatteryVoltageMv();
@ -1475,7 +1483,7 @@ void setup()
AsyncWiFiManager wifiManager(&server, &dns); AsyncWiFiManager wifiManager(&server, &dns);
// Memory optimizations for WiFiManager // Memory optimizations for WiFiManager
wifiManager.setDebugOutput(true); // Disable debug strings wifiManager.setDebugOutput(false); // Disable debug strings
// Reduce timeouts to free memory faster // Reduce timeouts to free memory faster
wifiManager.setTimeout(180); // Reduced from 180 wifiManager.setTimeout(180); // Reduced from 180
@ -1493,7 +1501,7 @@ void setup()
{ {
init_webserver(); init_webserver();
server.begin(); server.begin();
Serial.println("Wifi init"); Serial.println(F("Wifi init"));
} }
else else
{ {
@ -1513,7 +1521,7 @@ void setup()
0); /* Core where the task should run */ 0); /* Core where the task should run */
lastInteraction = millis(); lastInteraction = millis();
Serial.println("Init done."); Serial.println(F("Init done."));
} }
void id_song_action(AsyncWebServerRequest *request) void id_song_action(AsyncWebServerRequest *request)
@ -1530,7 +1538,7 @@ void id_song_action(AsyncWebServerRequest *request)
} }
} }
lastInteraction = millis(); lastInteraction = millis();
request->send_P(200, txt_plain, "ok"); request->send_P(200, txt_plain, PSTR("ok"));
} }
void progress_action(AsyncWebServerRequest *request) void progress_action(AsyncWebServerRequest *request)
@ -1548,7 +1556,7 @@ void progress_action(AsyncWebServerRequest *request)
} }
} }
lastInteraction = millis(); lastInteraction = millis();
request->send_P(200, txt_plain, "ok"); request->send_P(200, txt_plain, PSTR("ok"));
} }
void volume_action(AsyncWebServerRequest *request) void volume_action(AsyncWebServerRequest *request)
@ -1566,7 +1574,7 @@ void volume_action(AsyncWebServerRequest *request)
} }
} }
lastInteraction = millis(); lastInteraction = millis();
request->send_P(200, txt_plain, "ok"); request->send_P(200, txt_plain, PSTR("ok"));
} }
const String getSysDir(const String filename) const String getSysDir(const String filename)
@ -1760,9 +1768,9 @@ void loop()
{ {
if (voltage_threshold_counter > 3) if (voltage_threshold_counter > 3)
{ {
Serial.print("deep sleep due to low volts ("); Serial.print(F("deep sleep due to low volts ("));
Serial.print(lastVoltage); Serial.print(lastVoltage);
Serial.print(") min: "); Serial.print(F(") min: "));
Serial.println(config.minVoltage); Serial.println(config.minVoltage);
lastInteraction = millis() - config.sleepMessageDelay; lastInteraction = millis() - config.sleepMessageDelay;
@ -1833,7 +1841,7 @@ void loop2(void *parameter)
} }
if (!loggingDone) if (!loggingDone)
{ {
Serial.println("loop2 started"); Serial.println(F("loop2 started"));
loggingDone = true; loggingDone = true;
} }
vTaskDelay(1); vTaskDelay(1);